├── .travis.yml ├── .gitignore ├── .npmignore ├── test ├── endpoint.js ├── suites │ ├── writer.js │ └── parser.js ├── resources │ ├── minimal.gexf │ ├── edge_viz.gexf │ ├── liststring.gexf │ ├── data.gexf │ ├── edge_data.gexf │ ├── case.gexf │ └── les_miserables.gexf ├── helpers.js └── browser │ ├── unit.html │ ├── mocha.css │ └── async.js ├── bower.json ├── LICENSE.txt ├── package.json ├── index.js ├── gulpfile.js ├── README.md └── src ├── writer.js └── parser.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | Notes.md 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .npmignore 3 | .travis.yml 4 | gulpfile.js 5 | node_modules 6 | test 7 | build 8 | -------------------------------------------------------------------------------- /test/endpoint.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gexf Node.js Tests Endpoint 3 | * ============================ 4 | * 5 | * File requiring unit tests suites. 6 | */ 7 | var tests = { 8 | parser: require('./suites/parser.js') 9 | }; 10 | -------------------------------------------------------------------------------- /test/suites/writer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gexf Writer Unit Tests 3 | * ======================= 4 | * 5 | * Testing the writing utilities of the gexf library. 6 | */ 7 | 8 | if (!('window' in this)) { 9 | var assert = require('assert'), 10 | gexf = require('../../index.js'), 11 | helpers = require('../helpers.js'), 12 | async = require('async'); 13 | } 14 | 15 | describe('Writer', function() { 16 | 17 | }); 18 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gexf", 3 | "main": "build/gexf.min.js", 4 | "version": "0.2.5", 5 | "homepage": "https://github.com/Yomguithereal/gexf", 6 | "authors": [ 7 | "Yomguithereal" 8 | ], 9 | "description": "Gexf library for JavaScript.", 10 | "keywords": [ 11 | "gexf", 12 | "parser", 13 | "writer", 14 | "xml", 15 | "graph" 16 | ], 17 | "license": "MIT", 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "bower_components", 22 | "test", 23 | "tests", 24 | "gulpfile.js" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/resources/minimal.gexf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Gexf.net 5 | A hello world! file 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gexf Unit Tests Helpers 3 | * ======================== 4 | * 5 | * Miscellaneous helper functions. 6 | */ 7 | 8 | if (!('window' in this)) { 9 | 10 | // Node.js helpers 11 | var fs = require('fs'), 12 | gexf = require('../index.js'); 13 | 14 | module.exports = { 15 | fetch: function(name, callback) { 16 | fs.readFile(__dirname + '/resources/' + name + '.gexf', {encoding: 'utf-8'}, function(err, data) { 17 | if (err) throw err; 18 | callback(gexf.parse(data)); 19 | }); 20 | } 21 | } 22 | } 23 | else { 24 | 25 | // Browser helpers 26 | var helpers = { 27 | fetch: function(name, callback) { 28 | gexf.fetch('../resources/' + name + '.gexf', callback); 29 | } 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /test/browser/unit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Mocha Test Runner 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2013-2014 Guillaume Plique, Sciences-Po médialab 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 4 | to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 5 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 10 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 12 | IN THE SOFTWARE. 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gexf", 3 | "version": "0.2.6", 4 | "description": "Gexf library for JavaScript.", 5 | "homepage": "http://github.com/Yomguithereal/gexf-parser", 6 | "main": "index.js", 7 | "keywords": [ 8 | "gexf", 9 | "parser", 10 | "writer", 11 | "xml", 12 | "graph" 13 | ], 14 | "bugs": "http://github.com/Yomguithereal/gexf-parser/issues", 15 | "author": { 16 | "name": "Guillaume Plique", 17 | "url": "http://github.com/Yomguithereal" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "http://github.com/Yomguithereal/gexf-parser.git" 22 | }, 23 | "licenses": [ 24 | { 25 | "type": "MIT", 26 | "url": "https://github.com/Yomguithereal/gexf-parser/blob/master/LICENSE.txt" 27 | } 28 | ], 29 | "scripts": { 30 | "test": "gulp test" 31 | }, 32 | "dependencies": { 33 | "xmldom": "~0.1.19" 34 | }, 35 | "devDependencies": { 36 | "async": "^0.9.0", 37 | "gulp": "^3.8.10", 38 | "gulp-concat": "^2.4.1", 39 | "gulp-header": "^1.2.2", 40 | "gulp-jshint": "^1.9.0", 41 | "gulp-mocha": "^1.1.1", 42 | "gulp-mocha-phantomjs": "^0.5.1", 43 | "gulp-uglify": "^1.0.1", 44 | "run-sequence": "^1.0.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/resources/edge_viz.gexf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Yomguithereal 5 | An edge viz test graph 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * GEXF Library Node Bindings 3 | * =========================== 4 | * 5 | * Author: PLIQUE Guillaume (Yomguithereal) 6 | * URL: https://github.com/Yomguithereal/gexf 7 | * Version: 0.2.3 8 | */ 9 | var DOMParser = require('xmldom').DOMParser, 10 | DOMImplementation = require('xmldom').DOMImplementation, 11 | XMLSerializer = require('xmldom').XMLSerializer, 12 | parser = require('./src/parser.js'), 13 | writer = require('./src/writer.js'); 14 | 15 | // Helpers 16 | function isPlainObject(v) { 17 | return v instanceof Object && 18 | !(v instanceof Array) && 19 | !(v instanceof Function); 20 | } 21 | 22 | function extend() { 23 | var i, 24 | k, 25 | res = {}, 26 | l = arguments.length; 27 | 28 | for (i = l - 1; i >= 0; i--) 29 | for (k in arguments[i]) 30 | if (res[k] && isPlainObject(arguments[i][k])) 31 | res[k] = extend(arguments[i][k], res[k]); 32 | else 33 | res[k] = arguments[i][k]; 34 | 35 | return res; 36 | } 37 | 38 | // Namespace 39 | var gexf = {}; 40 | 41 | Object.defineProperty(gexf, 'version', { 42 | value: '0.2.5' 43 | }); 44 | 45 | gexf.parse = function(string) { 46 | var p = new DOMParser(); 47 | var xml = p.parseFromString(string, 'application/xml'); 48 | return parser.parse(xml); 49 | } 50 | 51 | gexf.create = function(params) { 52 | 53 | // Forcing implementation 54 | return writer.create.call(writer, extend( 55 | { 56 | implementation: DOMImplementation.prototype, 57 | serializer: XMLSerializer 58 | }, 59 | params 60 | )); 61 | } 62 | 63 | module.exports = gexf; 64 | -------------------------------------------------------------------------------- /test/resources/liststring.gexf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Gephi.org 5 | A Web network 6 | 7 | 8 | 9 | 10 | 11 | 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /test/resources/data.gexf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Gephi.org 5 | A Web network 6 | 7 | 8 | 9 | 10 | 11 | 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | jshint = require('gulp-jshint'), 3 | concat = require('gulp-concat'), 4 | header = require('gulp-header'), 5 | uglify = require('gulp-uglify'), 6 | mocha = require('gulp-mocha'), 7 | phantom = require('gulp-mocha-phantomjs'), 8 | seq = require('run-sequence'), 9 | pkg = require('./package.json'); 10 | 11 | var jsFiles = [ 12 | './src/*.js' 13 | ]; 14 | 15 | // Linting 16 | gulp.task('lint', function() { 17 | return gulp.src(jsFiles) 18 | .pipe(jshint()) 19 | .pipe(jshint.reporter('default')); 20 | }); 21 | 22 | // Building 23 | var h = '/* gexf<%= sub %>.js - <%= description %> - Version: <%= version %> - Author: <%= author.name %> - medialab SciencesPo */\n'; 24 | 25 | gulp.task('build-parser', function() { 26 | return gulp.src('./src/parser.js') 27 | .pipe(concat('gexf-parser.min.js')) 28 | .pipe(uglify()) 29 | .pipe(header(h, { 30 | sub: '-parser', 31 | description: 'Gexf parser for JavaScript.', 32 | version: pkg.version, 33 | author: pkg.author 34 | })) 35 | .pipe(gulp.dest('./build')); 36 | }); 37 | 38 | gulp.task('build-writer', function() { 39 | return gulp.src('./src/writer.js') 40 | .pipe(concat('gexf-writer.min.js')) 41 | .pipe(uglify()) 42 | .pipe(header(h, { 43 | sub: '-writer', 44 | description: 'Gexf writer for JavaScript.', 45 | version: pkg.version, 46 | author: pkg.author 47 | })) 48 | .pipe(gulp.dest('./build')); 49 | }); 50 | 51 | gulp.task('build-all', function() { 52 | return gulp.src(jsFiles) 53 | .pipe(concat('gexf.min.js')) 54 | .pipe(uglify()) 55 | .pipe(header(h, { 56 | sub: '', 57 | description: pkg.description, 58 | version: pkg.version, 59 | author: pkg.author 60 | })) 61 | .pipe(gulp.dest('./build')); 62 | }); 63 | 64 | // Tests 65 | gulp.task('node-test', function() { 66 | return gulp.src('./test/endpoint.js') 67 | .pipe(mocha({reporter: 'spec'})); 68 | }); 69 | 70 | gulp.task('browser-test', function() { 71 | return gulp.src('./test/browser/unit.html') 72 | .pipe(phantom({reporter: 'spec'})); 73 | }); 74 | 75 | // Macro-task 76 | gulp.task('test', function() { 77 | return seq('node-test', 'browser-test'); 78 | }); 79 | 80 | gulp.task('build', ['build-parser', 'build-writer', 'build-all']); 81 | 82 | gulp.task('default', function() { 83 | return seq('lint', 'test', 'build'); 84 | }); 85 | -------------------------------------------------------------------------------- /test/resources/edge_data.gexf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Gephi.org 5 | A Web network 6 | 7 | 8 | 9 | 10 | 11 | 12 | true 13 | 14 | 15 | 16 | 17 | likes 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /test/browser/mocha.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | margin:0; 5 | } 6 | 7 | #mocha { 8 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | margin: 60px 50px; 10 | } 11 | 12 | #mocha ul, 13 | #mocha li { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | #mocha ul { 19 | list-style: none; 20 | } 21 | 22 | #mocha h1, 23 | #mocha h2 { 24 | margin: 0; 25 | } 26 | 27 | #mocha h1 { 28 | margin-top: 15px; 29 | font-size: 1em; 30 | font-weight: 200; 31 | } 32 | 33 | #mocha h1 a { 34 | text-decoration: none; 35 | color: inherit; 36 | } 37 | 38 | #mocha h1 a:hover { 39 | text-decoration: underline; 40 | } 41 | 42 | #mocha .suite .suite h1 { 43 | margin-top: 0; 44 | font-size: .8em; 45 | } 46 | 47 | #mocha .hidden { 48 | display: none; 49 | } 50 | 51 | #mocha h2 { 52 | font-size: 12px; 53 | font-weight: normal; 54 | cursor: pointer; 55 | } 56 | 57 | #mocha .suite { 58 | margin-left: 15px; 59 | } 60 | 61 | #mocha .test { 62 | margin-left: 15px; 63 | overflow: hidden; 64 | } 65 | 66 | #mocha .test.pending:hover h2::after { 67 | content: '(pending)'; 68 | font-family: arial, sans-serif; 69 | } 70 | 71 | #mocha .test.pass.medium .duration { 72 | background: #c09853; 73 | } 74 | 75 | #mocha .test.pass.slow .duration { 76 | background: #b94a48; 77 | } 78 | 79 | #mocha .test.pass::before { 80 | content: '✓'; 81 | font-size: 12px; 82 | display: block; 83 | float: left; 84 | margin-right: 5px; 85 | color: #00d6b2; 86 | } 87 | 88 | #mocha .test.pass .duration { 89 | font-size: 9px; 90 | margin-left: 5px; 91 | padding: 2px 5px; 92 | color: #fff; 93 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 94 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 95 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 96 | -webkit-border-radius: 5px; 97 | -moz-border-radius: 5px; 98 | -ms-border-radius: 5px; 99 | -o-border-radius: 5px; 100 | border-radius: 5px; 101 | } 102 | 103 | #mocha .test.pass.fast .duration { 104 | display: none; 105 | } 106 | 107 | #mocha .test.pending { 108 | color: #0b97c4; 109 | } 110 | 111 | #mocha .test.pending::before { 112 | content: '◦'; 113 | color: #0b97c4; 114 | } 115 | 116 | #mocha .test.fail { 117 | color: #c00; 118 | } 119 | 120 | #mocha .test.fail pre { 121 | color: black; 122 | } 123 | 124 | #mocha .test.fail::before { 125 | content: '✖'; 126 | font-size: 12px; 127 | display: block; 128 | float: left; 129 | margin-right: 5px; 130 | color: #c00; 131 | } 132 | 133 | #mocha .test pre.error { 134 | color: #c00; 135 | max-height: 300px; 136 | overflow: auto; 137 | } 138 | 139 | /** 140 | * (1): approximate for browsers not supporting calc 141 | * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border) 142 | * ^^ seriously 143 | */ 144 | #mocha .test pre { 145 | display: block; 146 | float: left; 147 | clear: left; 148 | font: 12px/1.5 monaco, monospace; 149 | margin: 5px; 150 | padding: 15px; 151 | border: 1px solid #eee; 152 | max-width: 85%; /*(1)*/ 153 | max-width: calc(100% - 42px); /*(2)*/ 154 | word-wrap: break-word; 155 | border-bottom-color: #ddd; 156 | -webkit-border-radius: 3px; 157 | -webkit-box-shadow: 0 1px 3px #eee; 158 | -moz-border-radius: 3px; 159 | -moz-box-shadow: 0 1px 3px #eee; 160 | border-radius: 3px; 161 | } 162 | 163 | #mocha .test h2 { 164 | position: relative; 165 | } 166 | 167 | #mocha .test a.replay { 168 | position: absolute; 169 | top: 3px; 170 | right: 0; 171 | text-decoration: none; 172 | vertical-align: middle; 173 | display: block; 174 | width: 15px; 175 | height: 15px; 176 | line-height: 15px; 177 | text-align: center; 178 | background: #eee; 179 | font-size: 15px; 180 | -moz-border-radius: 15px; 181 | border-radius: 15px; 182 | -webkit-transition: opacity 200ms; 183 | -moz-transition: opacity 200ms; 184 | transition: opacity 200ms; 185 | opacity: 0.3; 186 | color: #888; 187 | } 188 | 189 | #mocha .test:hover a.replay { 190 | opacity: 1; 191 | } 192 | 193 | #mocha-report.pass .test.fail { 194 | display: none; 195 | } 196 | 197 | #mocha-report.fail .test.pass { 198 | display: none; 199 | } 200 | 201 | #mocha-report.pending .test.pass, 202 | #mocha-report.pending .test.fail { 203 | display: none; 204 | } 205 | #mocha-report.pending .test.pass.pending { 206 | display: block; 207 | } 208 | 209 | #mocha-error { 210 | color: #c00; 211 | font-size: 1.5em; 212 | font-weight: 100; 213 | letter-spacing: 1px; 214 | } 215 | 216 | #mocha-stats { 217 | position: fixed; 218 | top: 15px; 219 | right: 10px; 220 | font-size: 12px; 221 | margin: 0; 222 | color: #888; 223 | z-index: 1; 224 | } 225 | 226 | #mocha-stats .progress { 227 | float: right; 228 | padding-top: 0; 229 | } 230 | 231 | #mocha-stats em { 232 | color: black; 233 | } 234 | 235 | #mocha-stats a { 236 | text-decoration: none; 237 | color: inherit; 238 | } 239 | 240 | #mocha-stats a:hover { 241 | border-bottom: 1px solid #eee; 242 | } 243 | 244 | #mocha-stats li { 245 | display: inline-block; 246 | margin: 0 5px; 247 | list-style: none; 248 | padding-top: 11px; 249 | } 250 | 251 | #mocha-stats canvas { 252 | width: 40px; 253 | height: 40px; 254 | } 255 | 256 | #mocha code .comment { color: #ddd; } 257 | #mocha code .init { color: #2f6fad; } 258 | #mocha code .string { color: #5890ad; } 259 | #mocha code .keyword { color: #8a6343; } 260 | #mocha code .number { color: #2f6fad; } 261 | 262 | @media screen and (max-device-width: 480px) { 263 | #mocha { 264 | margin: 60px 0px; 265 | } 266 | 267 | #mocha #stats { 268 | position: absolute; 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /test/resources/case.gexf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | polinode.com 5 | Survey One 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/Yomguithereal/gexf.svg)](https://travis-ci.org/Yomguithereal/gexf) 2 | 3 | # GEXF JavaScript Library 4 | 5 | ## DEPRECATION NOTICE 6 | 7 | This library, while probably still working, should be considered unmaintained and has been replaced by [`graphology-gexf`](https://graphology.github.io/standard-library/gexf), the gexf parser/writer utilities of [`graphology`](https://graphology.github.io/). 8 | 9 | ## Description 10 | This gexf library is designed to parse and write [gexf](https://gephi.org/gexf/format/) files. It can be used either client-side or with node. 11 | 12 | It was originally developed to be used with [sigma](https://github.com/jacomyal/sigma.js) and can be compiled as a [sigma plugin](https://github.com/jacomyal/sigma.js/tree/master/plugins/sigma.parsers.gexf). 13 | 14 | ## Summary 15 | 16 | * [Usage](#usage) 17 | * [Client-side](#client-side) 18 | * [Node.js](#nodejs) 19 | * [Build](#build) 20 | * [Output data](#output-data) 21 | * [Writer](#writer) 22 | 23 | ## Usage 24 | 25 | ### Client-side 26 | The gexf can either be used to fetch and parse the .gexf file or just to parse it if you want to fetch it by your own means. The parser adds a `gexf` variable to your global scope so you can use it. 27 | 28 | **Fetching and parsing** 29 | ```js 30 | // Synchronously fetch the gexf file and parse it 31 | var graph = gexf.fetch('/url/to/file.gexf'); 32 | 33 | // Asynchronously fetch the gexf file and parse it 34 | gexf.fetch('/url/to/file.gexf', function(graph) { 35 | console.log(graph); 36 | }); 37 | ``` 38 | 39 | **Parsing only** 40 | 41 | If you want to fetch the gexf yourself, you can still parse the graph by providing a javascript DOM object to the parser (an ajax XML response or a parsed string, for instance). 42 | ```js 43 | // Converting a string to a DOM object 44 | var gexf_dom = new DOMParser().parseFromString(gexf_string, "application/xml"); 45 | 46 | // Parsing the gexf 47 | var graph = gexf.parse(gexf_dom); 48 | ``` 49 | 50 | **Writing** 51 | 52 | For more precisions, refer to the [writer](#writer) section of the current documentation. 53 | 54 | ```js 55 | var myGexf = gexf.create([params]); 56 | ``` 57 | 58 | ###Node.js 59 | 60 | **Installation** 61 | ``` 62 | # For the latest released version 63 | npm install gexf 64 | 65 | # For the development version 66 | npm install git+https://github.com/Yomguithereal/gexf.git 67 | ``` 68 | 69 | **Parsing** 70 | ```js 71 | var fs = require('fs'), 72 | gexf = require('gexf'); 73 | 74 | // Reading your gexf file 75 | var gexf_file = fs.readFileSync('/path/to/your.gexf', 'utf-8'); 76 | 77 | // Parsing it 78 | var graph = gexf.parse(gexf_file); 79 | ``` 80 | 81 | **Writing** 82 | 83 | For more precisions, refer to the [writer](#writer) section of the current documentation. 84 | 85 | ```js 86 | var gexf = require('gexf'); 87 | 88 | var myGexf = gexf.create([params]); 89 | ``` 90 | 91 | ## Build 92 | If you want to build the minified client version, clone this repo and launch the build task. 93 | 94 | ```bash 95 | git clone git@github.com:Yomguithereal/gexf.git 96 | cd gexf 97 | npm install 98 | gulp build 99 | ``` 100 | 101 | ## Output Data 102 | The following example shows what the parser is able to output given a gexf file. 103 | 104 | ```js 105 | { 106 | version: "1.0.1", 107 | meta: { 108 | creator: "Yomguithereal", 109 | lastmodifieddate: "2010-05-29+01:27", 110 | title: "A random graph" 111 | }, 112 | defaultEdgeType: "directed", 113 | model: { 114 | node: [ 115 | { 116 | id: "authority", 117 | type: "float", 118 | title: "Authority" 119 | }, 120 | { 121 | id: "name", 122 | type: "string", 123 | title: "Author's name" 124 | } 125 | ] 126 | }, 127 | nodes: [ 128 | { 129 | id: "0", 130 | label: "Myriel", 131 | attributes: { 132 | authority: 10.43, 133 | name: "Myriel Dafault" 134 | }, 135 | viz: { 136 | color: "rgb(216,72,45)", 137 | size: 22.4, 138 | position: { 139 | x: 234, 140 | y: 23, 141 | z: 0 142 | } 143 | } 144 | }, 145 | { 146 | id: "1", 147 | label: "Jean", 148 | attributes: { 149 | authority: 2.43, 150 | name: "Jean Daguerre" 151 | }, 152 | viz: { 153 | color: "rgb(255,72,45)", 154 | size: 21.4, 155 | position: { 156 | x: 34, 157 | y: 23, 158 | z: 0 159 | } 160 | } 161 | } 162 | ], 163 | edges: [ 164 | { 165 | id: "0", 166 | source: "0", 167 | target: "1", 168 | type: "directed", 169 | weight: 1, 170 | viz: { 171 | shape: "dotted" 172 | } 173 | } 174 | ] 175 | } 176 | ``` 177 | 178 | ## Writer 179 | 180 | Note that the data format expected by the writer is exactly the same as the one outputted by the parser. 181 | 182 | This means that theoritically - i.e. "if I did my job correctly" - you can give the result graph from parsing a gexf file and give it to the writer to create an identical file. 183 | 184 | ### Instantiation 185 | 186 | To create a writer instance, just do the following: 187 | 188 | ```js 189 | var myGexf = gexf.create([params]); 190 | ``` 191 | 192 | *Parameters* 193 | 194 | Possible parameters are: 195 | 196 | * **meta** *?object*: an object of metadata for the graph. 197 | * **defaultEdgeType** *?string* [`'undirected'`]: default edge type. 198 | * **encoding** *?string* [`'UTF-8'`]: encoding of the XML file. 199 | * **mode** *?string*: mode of the graph. `static` or `dynamic` for instance. 200 | * **model** *?object*: an object containing the models of the nodes and/or edges. 201 | * **node** *?array*: array of node possible attributes. see [output data](#output-data) for precisions. 202 | * **edge** *?array*: array of edge possible attributes. see [output data](#output-data) for precisions. 203 | * **nodes** *?array*: array of nodes to pass at instantiation time. 204 | * **edges** *?array*: array of edges to pass at instantiation time. 205 | * **implementation** *?DOMImplementation*: the DOM implementation to build the XML document. Will take the browser's one by default of xmldom's one in node. 206 | * **serializer** *?XMLSerializer*: the XMLSerializer class to serialize the XML document. Will default to the browser's one or xmldom's one in node. 207 | * **namespace** *?string* [`'http://www.gexf.net/1.2draft'`]: gexf XML namespace to use. 208 | * **vizNamespace** *?string* [`'http:///www.gexf.net/1.2draft/viz'`]: gexf viz XML namespace to use. 209 | * **version** *?string* [`'1.2'`]: version of gexf to produce. 210 | 211 | ### Methods 212 | 213 | *addNode* 214 | 215 | Adding a single node to the gexf document. 216 | 217 | ```js 218 | myGexf.addNode({ 219 | id: 'n01', 220 | label: 'myFirstNode', 221 | attributes: { 222 | name: 'John', 223 | surname: 'Silver' 224 | }, 225 | viz: { 226 | color: 'rgb(255, 234, 45)' 227 | } 228 | }); 229 | ``` 230 | 231 | *addEdge* 232 | 233 | Adding a single edge to the gexf document. 234 | 235 | ```js 236 | myGexf.addEdge({ 237 | id: 'e01', 238 | source: 'n01', 239 | target: 'n02', 240 | attributes: { 241 | predicate: 'LIKES' 242 | }, 243 | viz: { 244 | thickness: 34 245 | } 246 | }); 247 | ``` 248 | 249 | *setMeta* 250 | 251 | Same as passing a `meta` parameter at instantiation. 252 | 253 | *setNodeModel* 254 | 255 | Same as passing a `models.node` parameter at instantiation. 256 | 257 | *setEdgeModel* 258 | 259 | Same as passing a `models.edge` parameter at instantiation. 260 | 261 | *addNodeAttribute* 262 | 263 | Add a single node attribute definition to the node model. 264 | 265 | *addEdgeAttribute* 266 | 267 | Add a single edge attribute definition to the edge model. 268 | 269 | *serialize* 270 | 271 | Produce the string representation of the gexf document. 272 | 273 | ### Retrieving the gexf 274 | 275 | ```js 276 | // As a document 277 | var doc = myGexf.document; 278 | 279 | // As a string 280 | var string = myGexf.serialize(); 281 | ``` 282 | 283 | ## Contribution 284 | Please feel free to contribute. To set up the dev environment you should have **nodejs**, **npm** and **gulp** installed. 285 | 286 | ```bash 287 | git clone git@github.com:Yomguithereal/gexf.git 288 | cd gexf 289 | npm install 290 | ``` 291 | 292 | Be sure to add relevant unit tests and pass the linter before submitting any change to the library. 293 | 294 | ```bash 295 | npm test 296 | ``` 297 | -------------------------------------------------------------------------------- /src/writer.js: -------------------------------------------------------------------------------- 1 | ;(function(document, undefined) { 2 | 'use strict'; 3 | 4 | /** 5 | * GEXF Writer 6 | * ============ 7 | * 8 | * Author: PLIQUE Guillaume (Yomguithereal) 9 | * URL: https://github.com/Yomguithereal/gexf 10 | * Version: 0.2.5 11 | */ 12 | 13 | /** 14 | * Constants 15 | */ 16 | var TYPES = [ 17 | 'integer', 18 | 'long', 19 | 'double', 20 | 'float', 21 | 'boolean', 22 | 'liststring', 23 | 'string', 24 | 'anyURI' 25 | ]; 26 | 27 | /** 28 | * Helpers 29 | */ 30 | function cast(type, value) { 31 | 32 | switch (type) { 33 | case 'boolean': 34 | case 'integer': 35 | case 'long': 36 | case 'float': 37 | case 'double': 38 | return '' + value; 39 | 40 | case 'liststring': 41 | if (value instanceof Array) 42 | return value.join('|'); 43 | } 44 | 45 | if (typeof value === 'object') 46 | throw Error('gexf.writer.cast: trying to cast an object to a string.'); 47 | 48 | return value; 49 | } 50 | 51 | function parseColor(val) { 52 | var result = [0, 0, 0]; 53 | 54 | if (val.match(/^#/)) { 55 | val = (val || '').replace(/^#/, ''); 56 | result = (val.length === 3) ? 57 | [ 58 | parseInt(val.charAt(0) + val.charAt(0), 16), 59 | parseInt(val.charAt(1) + val.charAt(1), 16), 60 | parseInt(val.charAt(2) + val.charAt(2), 16) 61 | ] : 62 | [ 63 | parseInt(val.charAt(0) + val.charAt(1), 16), 64 | parseInt(val.charAt(2) + val.charAt(3), 16), 65 | parseInt(val.charAt(4) + val.charAt(5), 16) 66 | ]; 67 | } else if (val.match(/^ *rgba? *\(/)) { 68 | val = val.match( 69 | /^ *rgba? *\( *([0-9]*) *, *([0-9]*) *, *([0-9]*) *(,.*)?\) *$/ 70 | ); 71 | result = [ 72 | +val[1], 73 | +val[2], 74 | +val[3] 75 | ]; 76 | 77 | if (val[4]) 78 | result.push(+val[4].replace(', ', '')); 79 | } 80 | 81 | return result; 82 | } 83 | 84 | 85 | /** 86 | * Main object 87 | */ 88 | function Gexf(params) { 89 | params = params || {}; 90 | 91 | var implementation = params.implementation || document.implementation; 92 | 93 | // Serializer? 94 | this.serializer = params.serializer ? 95 | new params.serializer() : 96 | new XMLSerializer(); 97 | 98 | // Creating document 99 | this.document = implementation.createDocument( 100 | 'http://www.gexf.net/1.2draft', 101 | 'gexf', 102 | null 103 | ); 104 | this.root = this.document.documentElement; 105 | 106 | // Assigning namespaces 107 | // TODO: version here also 108 | this.xmlns = params.namespace || 'http://www.gexf.net/1.2draft'; 109 | this.vizXmlns = params.vizNamespace || 'http:///www.gexf.net/1.2draft/viz'; 110 | this.root.setAttribute('xmlns', 111 | this.xmlns); 112 | this.root.setAttribute('xmlns:xsi', 113 | 'http://www.w3.org/2001/XMLSchema-instance'); 114 | this.root.setAttribute('xsi:schemaLocation', 115 | 'http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd'); 116 | 117 | this.hasViz = false; 118 | 119 | // Version 120 | this.root.setAttribute('version', params.version || '1.2'); 121 | 122 | // Encoding 123 | this.encoding = params.encoding || 'UTF-8'; 124 | 125 | // Metas 126 | if (params.meta) 127 | this.setMeta(params.meta); 128 | 129 | // Graph 130 | this.graph = this.createElement('graph', { 131 | defaultedgetype: params.defaultEdgeType || 'undirected', 132 | mode: params.mode 133 | }); 134 | this.root.appendChild(this.graph); 135 | 136 | // Model 137 | this.model = { 138 | node: {}, 139 | edge: {} 140 | }; 141 | 142 | this.nodeAttributes = null; 143 | this.edgeAttributes = null; 144 | 145 | if (params.model && params.model.node) 146 | this.setNodeModel(params.model.node); 147 | if (params.model && params.model.edge) 148 | this.setEdgeModel(params.model.edge); 149 | 150 | // Nodes & Edges 151 | this.nodes = this.createElement('nodes'); 152 | this.edges = this.createElement('edges'); 153 | 154 | this.graph.appendChild(this.nodes); 155 | this.graph.appendChild(this.edges); 156 | 157 | var i, 158 | l; 159 | 160 | if (params.nodes) { 161 | for (i = 0, l = params.nodes.length; i < l; i++) 162 | this.addNode(params.nodes[i]); 163 | } 164 | 165 | if (params.edges) { 166 | for (i = 0, l = params.edges.length; i < l; i++) 167 | this.addEdge(params.edges[i]); 168 | } 169 | } 170 | 171 | /** 172 | * Prototype 173 | */ 174 | Gexf.prototype.createElement = function(tag, value, attributes) { 175 | if (!tag) 176 | throw Error('gexf.writer.createElement: wrong arguments.'); 177 | 178 | if (typeof value === 'object') { 179 | attributes = value; 180 | value = null; 181 | } 182 | 183 | var node = this.document.createElement(tag); 184 | 185 | if (value) { 186 | var text = this.document.createTextNode(value); 187 | node.appendChild(text); 188 | } 189 | 190 | if (attributes) 191 | for (var k in attributes) 192 | if (typeof attributes[k] !== 'undefined' && attributes[k] !== null) 193 | node.setAttribute(k, attributes[k]); 194 | 195 | return node; 196 | }; 197 | 198 | Gexf.prototype.setMeta = function(o) { 199 | o = o || {}; 200 | 201 | var meta = this.document.createElement('meta'), 202 | m, 203 | n, 204 | t; 205 | 206 | for (m in o) { 207 | if (m === 'lastmodifieddate') { 208 | meta.setAttribute('lastmodifieddate', o[m]); 209 | } 210 | else { 211 | meta.appendChild(this.createElement(m, o[m])); 212 | } 213 | } 214 | 215 | // Appending meta to document 216 | this.root.appendChild(meta); 217 | 218 | return this; 219 | }; 220 | 221 | Gexf.prototype.setModel = function(cls, model) { 222 | model = model || []; 223 | 224 | if (cls !== 'node' && cls !== 'edge') 225 | throw Error('gexf.writer.setModel: wrong model cls "' + cls + '"'); 226 | 227 | if (!(model instanceof Array)) 228 | throw Error('gexf.writer.setModel: model is not a valid array.'); 229 | 230 | // Reset model 231 | this.model[cls] = {}; 232 | 233 | // Adding the attributes 234 | var attributes = this.createElement('attributes', {class: cls}); 235 | 236 | // Checking whether the model is to be reset 237 | var prop = cls + 'Attributes'; 238 | 239 | if (this[prop]) 240 | this.graph.removeChild(this[prop]); 241 | 242 | this[prop] = attributes; 243 | 244 | this.graph.insertBefore(attributes, this.nodes || this.edges); 245 | 246 | // Creating attribute nodes 247 | var i, 248 | l; 249 | 250 | for (i = 0, l = model.length; i < l; i++) 251 | this.addAttribute(cls, model[i]); 252 | 253 | return this; 254 | }; 255 | 256 | Gexf.prototype.setNodeModel = function(model) { 257 | return this.setModel('node', model); 258 | }; 259 | 260 | Gexf.prototype.setEdgeModel = function(model) { 261 | return this.setModel('edge', model); 262 | }; 263 | 264 | Gexf.prototype.addAttribute = function(cls, def) { 265 | 266 | if (cls !== 'node' && cls !== 'edge') 267 | throw Error('gexf.writer.addAttribute: wrong model cls "' + cls + '"'); 268 | 269 | if (!def) 270 | throw Error('gexf.writer.addAttribute: wrong arguments.'); 271 | 272 | if (!this[cls + 'Attributes']) 273 | return this.setModel(cls, [def]); 274 | 275 | var type = def.type || 'string'; 276 | 277 | if (!~TYPES.indexOf(type)) 278 | throw Error('gexf.writer.addAttribute: unknown attribute type "' + type + '"'); 279 | 280 | // Adding to model 281 | this.model[cls][def.id] = def; 282 | 283 | var attribute = this.createElement('attribute', { 284 | id: def.id, 285 | title: def.title, 286 | type: type 287 | }); 288 | 289 | // Default value? 290 | if (typeof def.defaultValue !== 'undefined') { 291 | var defaultValue = this.createElement('default', def.defaultValue); 292 | attribute.appendChild(defaultValue); 293 | } 294 | 295 | this[cls + 'Attributes'].appendChild(attribute); 296 | return this; 297 | }; 298 | 299 | Gexf.prototype.addNodeAttribute = function(def) { 300 | return this.addAttribute('node', def); 301 | }; 302 | 303 | Gexf.prototype.addEdgeAttribute = function(def) { 304 | return this.addAttribute('edge', def); 305 | }; 306 | 307 | Gexf.prototype.addNode = function(n) { 308 | var k, 309 | a, 310 | m; 311 | 312 | if (typeof n.id === 'undefined' || n.id === null) 313 | throw Error('gexf.writer.addNode: inexistent id.'); 314 | 315 | // Creating element 316 | var node = this.createElement('node', { 317 | id: n.id, 318 | label: n.label 319 | }); 320 | 321 | // Attributes 322 | if (n.attributes && Object.keys(n.attributes).length > 0) { 323 | var attvalues = this.createElement('attvalues'); 324 | 325 | for (k in n.attributes || {}) { 326 | a = n.attributes[k]; 327 | m = this.model.node[k]; 328 | 329 | if (!m) 330 | throw Error('gexf.writer.addNode: property "' + k + '" not registered in node model.'); 331 | 332 | var attvalue = this.createElement('attvalue', { 333 | 'for': m.id, 334 | value: cast(m.type, a) 335 | }); 336 | 337 | attvalues.appendChild(attvalue); 338 | } 339 | 340 | node.appendChild(attvalues); 341 | } 342 | 343 | // Viz 344 | if (n.viz) { 345 | 346 | if (!this.hasViz) { 347 | this.hasViz = true; 348 | this.root.setAttribute('xmlns:viz', this.vizXmlns); 349 | } 350 | 351 | if (n.viz.color) { 352 | var rgba = parseColor(n.viz.color); 353 | 354 | var color = this.createElement('viz:color', { 355 | r: rgba[0], 356 | g: rgba[1], 357 | b: rgba[2], 358 | a: rgba[3] 359 | }); 360 | 361 | node.appendChild(color); 362 | } 363 | 364 | if (n.viz.position) { 365 | var position = this.createElement('viz:position', { 366 | x: n.viz.position.x, 367 | y: n.viz.position.y, 368 | z: n.viz.position.z 369 | }); 370 | 371 | node.appendChild(position); 372 | } 373 | 374 | if (n.viz.size) { 375 | var size = this.createElement('viz:size', { 376 | value: n.viz.size 377 | }); 378 | 379 | node.appendChild(size); 380 | } 381 | 382 | if (n.viz.shape) { 383 | var shape = this.createElement('viz:shape', { 384 | value: n.viz.shape 385 | }); 386 | 387 | node.appendChild(shape); 388 | } 389 | } 390 | 391 | // Appending node 392 | this.nodes.appendChild(node); 393 | return this; 394 | }; 395 | 396 | Gexf.prototype.addEdge = function(e) { 397 | var k, 398 | a, 399 | m; 400 | 401 | // Creating element 402 | var edge = this.createElement('edge', { 403 | id: e.id, 404 | label: e.label, 405 | weight: e.weight, 406 | type: e.type, 407 | source: e.source, 408 | target: e.target 409 | }); 410 | 411 | // Attributes 412 | if (e.attributes && Object.keys(e.attributes).length > 0) { 413 | var attvalues = this.createElement('attvalues'); 414 | 415 | for (k in e.attributes || {}) { 416 | a = e.attributes[k]; 417 | m = this.model.edge[k]; 418 | 419 | if (!m) 420 | throw Error('gexf.writer.addEdge: property "' + k + '" not registered in edge model.'); 421 | 422 | var attvalue = this.createElement('attvalue', { 423 | 'for': m.id, 424 | value: cast(m.type, a) 425 | }); 426 | 427 | attvalues.appendChild(attvalue); 428 | } 429 | 430 | edge.appendChild(attvalues); 431 | } 432 | 433 | // Viz 434 | if (e.viz) { 435 | 436 | if (!this.hasViz) { 437 | this.hasViz = true; 438 | this.root.setAttribute('xmlns:viz', this.vizXmlns); 439 | } 440 | 441 | if (e.viz.color) { 442 | var rgba = parseColor(e.viz.color); 443 | 444 | var color = this.createElement('viz:color', { 445 | r: rgba[0], 446 | g: rgba[1], 447 | b: rgba[2], 448 | a: rgba[3] 449 | }); 450 | 451 | edge.appendChild(color); 452 | } 453 | 454 | if (e.viz.shape) { 455 | var shape = this.createElement('viz:shape', { 456 | value: e.viz.shape 457 | }); 458 | 459 | edge.appendChild(shape); 460 | } 461 | 462 | if (e.viz.thickness) { 463 | var thickness = this.createElement('viz:thickness', { 464 | value: e.viz.thickness 465 | }); 466 | 467 | edge.appendChild(thickness); 468 | } 469 | } 470 | 471 | // Appending edge 472 | this.edges.appendChild(edge); 473 | return this; 474 | }; 475 | 476 | Gexf.prototype.serialize = function() { 477 | return '' + 478 | this.serializer.serializeToString(this.document); 479 | }; 480 | 481 | /** 482 | * Public interface 483 | * ----------------- 484 | */ 485 | function create(params) { 486 | return new Gexf(params); 487 | } 488 | 489 | /** 490 | * Exporting 491 | * ---------- 492 | */ 493 | var gexf = { 494 | 495 | // Functions 496 | create: create, 497 | 498 | // Version 499 | version: '0.2.5' 500 | }; 501 | 502 | if (typeof exports !== 'undefined') { 503 | if (typeof module !== 'undefined' && module.exports) 504 | exports = module.exports = gexf; 505 | exports.gexf = gexf; 506 | } 507 | else if (typeof define === 'function' && define.amd) { 508 | define('gexf', [], function() { 509 | return gexf; 510 | }); 511 | } 512 | else { 513 | 514 | if (typeof this.gexf !== 'undefined') { 515 | 516 | // Extending 517 | this.gexf.create = create; 518 | } 519 | else { 520 | 521 | // Creating 522 | this.gexf = gexf; 523 | } 524 | } 525 | 526 | }).call(this, 'document' in this ? this.document : {}); 527 | -------------------------------------------------------------------------------- /test/suites/parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gexf Parser Unit Tests 3 | * ======================= 4 | * 5 | * Testing the parsing utilities of the gexf library. 6 | */ 7 | 8 | if (!('window' in this)) { 9 | var assert = require('assert'), 10 | gexf = require('../../index.js'), 11 | helpers = require('../helpers.js'), 12 | async = require('async'); 13 | } 14 | 15 | describe('Parser', function() { 16 | this.timeout(5000); 17 | 18 | // Collection of tests expected results 19 | var tests = [ 20 | { 21 | title: 'Minimal Graph', 22 | gexf: 'minimal', 23 | basics: { 24 | version: '1.2', 25 | mode: 'static', 26 | defaultEdgeType: 'directed', 27 | meta: { 28 | creator: 'Gexf.net', 29 | description: 'A hello world! file', 30 | lastmodifieddate: '2009-03-20' 31 | }, 32 | nodes_nb: 2, 33 | node_test: { 34 | id: 0, 35 | node: { 36 | id: '0', 37 | label: 'Hello' 38 | } 39 | }, 40 | edges_nb: 1, 41 | edge_test: { 42 | id: 0, 43 | edge: { 44 | id: '0', 45 | label: '', 46 | source: '0', 47 | target: '1', 48 | type: 'directed', 49 | weight: 1 50 | } 51 | } 52 | } 53 | }, 54 | { 55 | title: 'Basic Graph', 56 | gexf: 'yeast', 57 | basics: { 58 | version: '1.1', 59 | mode: 'static', 60 | defaultEdgeType: 'undirected', 61 | meta: {}, 62 | nodes_nb: 2361, 63 | node_test: { 64 | id: 502, 65 | node: { 66 | id: '5443', 67 | label: 'YDR283C' 68 | } 69 | }, 70 | edges_nb: 7182, 71 | edge_test: { 72 | id: 1300, 73 | edge: { 74 | id: '14488', 75 | label: '', 76 | source: '5096', 77 | target: '6882', 78 | type: 'undirected', 79 | weight: 1 80 | } 81 | } 82 | } 83 | }, 84 | { 85 | title: 'Data Graph', 86 | gexf: 'data', 87 | basics: { 88 | version: '1.2', 89 | mode: 'static', 90 | defaultEdgeType: 'directed', 91 | meta: { 92 | creator: 'Gephi.org', 93 | description: 'A Web network', 94 | lastmodifieddate: '2009-03-20' 95 | }, 96 | model: [ 97 | {id: '0', title: 'url', type: 'string'}, 98 | {id: '1', title: 'indegree', type: 'float'}, 99 | {id: '2', title: 'frog', type: 'boolean', defaultValue: 'true'} 100 | ], 101 | nodes_nb: 4, 102 | node_test: { 103 | id: 1, 104 | node: { 105 | id: '1', 106 | label: 'Webatlas', 107 | attributes: { 108 | '0': 'http://webatlas.fr', 109 | '1': 2, 110 | '2': true 111 | } 112 | } 113 | }, 114 | edges_nb: 5, 115 | edge_test: { 116 | id: 3, 117 | edge: { 118 | id: '3', 119 | label: '', 120 | source: '2', 121 | target: '1', 122 | type: 'directed', 123 | weight: 1 124 | } 125 | } 126 | } 127 | }, 128 | { 129 | title: 'Viz Graph', 130 | gexf: 'arctic', 131 | basics: { 132 | version: '1.0', 133 | mode: 'static', 134 | defaultEdgeType: 'undirected', 135 | meta: {}, 136 | model: [ 137 | {id: '0', title: 'nodedef', type: 'string'}, 138 | {id: '1', title: 'label', type: 'string'}, 139 | {id: '2', title: 'occurrences', type: 'integer'} 140 | ], 141 | nodes_nb: 1715, 142 | node_test: { 143 | id: 1100, 144 | node: { 145 | id: '1102', 146 | label: 'Interglacial Period', 147 | attributes: { 148 | '0': 'n1102', 149 | '1': 'Interglacial Period', 150 | '2': 3 151 | }, 152 | viz: { 153 | color: 'rgb(153,255,255)', 154 | position: { 155 | x: -31.175037, 156 | y: 179.857, 157 | z: 0 158 | }, 159 | size: 3.6317973 160 | } 161 | } 162 | }, 163 | edges_nb: 6676, 164 | edge_test: { 165 | id: 305, 166 | edge: { 167 | id: '305', 168 | label: '', 169 | source: '263', 170 | target: '113', 171 | type: 'undirected', 172 | weight: 1, 173 | viz: {} 174 | } 175 | } 176 | } 177 | }, 178 | { 179 | title: 'Celegans Graph', 180 | gexf: 'celegans', 181 | basics: { 182 | version: '1.1', 183 | mode: 'static', 184 | defaultEdgeType: 'undirected', 185 | meta: {}, 186 | nodes_nb: 306, 187 | node_test: { 188 | id: 203, 189 | node: { 190 | id: '281', 191 | label: '282' 192 | } 193 | }, 194 | edges_nb: 2345, 195 | edge_test: { 196 | id: 1602, 197 | edge: { 198 | id: '285', 199 | label: '', 200 | source: '38', 201 | target: '302', 202 | type: 'undirected', 203 | weight: 2 204 | } 205 | } 206 | } 207 | }, 208 | { 209 | title: 'Les Misérables Graph', 210 | gexf: 'les_miserables', 211 | basics: { 212 | version: '1.1', 213 | mode: 'static', 214 | defaultEdgeType: 'directed', 215 | meta: { 216 | creator: 'ofNodesAndEdges.com', 217 | title: 'Les Misérables, the characters coappearance weighted graph', 218 | lastmodifieddate: '2010-05-29+01:27' 219 | }, 220 | model: [ 221 | {id: 'authority', title: 'Authority', type: 'float'}, 222 | {id: 'hub', title: 'Hub', type: 'float'} 223 | ], 224 | nodes_nb: 77, 225 | node_test: { 226 | id: 5, 227 | node: { 228 | id: '5.0', 229 | label: 'Geborand', 230 | attributes: { 231 | authority: 0.0034188034, 232 | hub: 0.0034188034 233 | }, 234 | viz: { 235 | color: 'rgb(179,0,0)', 236 | position: { 237 | x: 318.6509, 238 | y: 85.41602, 239 | z: 0 240 | }, 241 | size: 15 242 | } 243 | } 244 | }, 245 | edges_nb: 254, 246 | edge_test: { 247 | id: 200, 248 | edge: { 249 | id: '198', 250 | label: '', 251 | source: '66.0', 252 | target: '62.0', 253 | type: 'directed', 254 | weight: 2, 255 | viz: {} 256 | } 257 | } 258 | } 259 | }, 260 | { 261 | title: 'Edge Viz Graph', 262 | gexf: 'edge_viz', 263 | basics: { 264 | version: '1.1', 265 | mode: 'static', 266 | defaultEdgeType: 'directed', 267 | meta: { 268 | creator: 'Yomguithereal', 269 | title: 'An edge viz test graph', 270 | lastmodifieddate: '2010-05-29+01:27' 271 | }, 272 | model: [ 273 | {id: 'authority', title: 'Authority', type: 'float'}, 274 | {id: 'hub', title: 'Hub', type: 'float'} 275 | ], 276 | nodes_nb: 2, 277 | node_test: { 278 | id: 0, 279 | node: { 280 | id: '0.0', 281 | label: 'Myriel', 282 | attributes: { 283 | authority: 0.01880342, 284 | hub: 0.01880342 285 | }, 286 | viz: { 287 | color: 'rgb(216,72,45)', 288 | position: { 289 | x: 268.72385, 290 | y: 91.18155, 291 | z: 0 292 | }, 293 | size: 22.714287 294 | } 295 | } 296 | }, 297 | edges_nb: 1, 298 | edge_test: { 299 | id: 0, 300 | edge: { 301 | id: '0', 302 | label: '', 303 | source: '1.0', 304 | target: '0.0', 305 | type: 'directed', 306 | weight: 1, 307 | viz: { 308 | color: 'rgba(179,0,0,0.5)', 309 | thickness: 2, 310 | shape: 'dotted' 311 | } 312 | } 313 | } 314 | } 315 | }, 316 | { 317 | title: 'Edge Data Graph', 318 | gexf: 'edge_data', 319 | basics: { 320 | version: '1.2', 321 | mode: 'static', 322 | defaultEdgeType: 'directed', 323 | meta: { 324 | creator: 'Gephi.org', 325 | description: 'A Web network', 326 | lastmodifieddate: '2009-03-20' 327 | }, 328 | model: [ 329 | {id: '0', title: 'url', type: 'string'}, 330 | {id: '1', title: 'indegree', type: 'float'}, 331 | {id: '2', title: 'frog', type: 'boolean', defaultValue: 'true'} 332 | ], 333 | edgeModel: [ 334 | {id: 'predicate', title: 'Predicate', type: 'string', defaultValue: 'likes'}, 335 | {id: 'confidence', title: 'Confidence', type: 'float'} 336 | ], 337 | nodes_nb: 4, 338 | node_test: { 339 | id: 1, 340 | node: { 341 | id: '1', 342 | label: 'Webatlas', 343 | attributes: { 344 | '0': 'http://webatlas.fr', 345 | '1': 2, 346 | '2': true 347 | } 348 | } 349 | }, 350 | edges_nb: 5, 351 | edge_test: { 352 | id: 3, 353 | edge: { 354 | id: '3', 355 | label: '', 356 | attributes: { 357 | predicate: 'likes', 358 | confidence: 0.88 359 | }, 360 | source: '2', 361 | target: '1', 362 | type: 'directed', 363 | weight: 1 364 | } 365 | } 366 | } 367 | }, 368 | { 369 | title: 'Case & Attributes Graph', 370 | gexf: 'case', 371 | basics: { 372 | version: '1.2', 373 | mode: 'static', 374 | defaultEdgeType: 'directed', 375 | meta: { 376 | creator: 'polinode.com', 377 | description: 'Survey One', 378 | lastmodifieddate: '02-05-2014' 379 | }, 380 | model: [ 381 | {id: 'name', title: 'name', type: 'string'}, 382 | {id: 'status', title: 'status', type: 'string'}, 383 | {id: 'list', title: 'list', type: 'string'}, 384 | {id: 'Gender', title: 'Gender', type: 'string'}, 385 | {id: 'Position', title: 'Position', type: 'string'} 386 | ], 387 | edgeModel: [ 388 | {id: 'Q1', title: 'Q1', type: 'string'} 389 | ], 390 | nodes_nb: 10, 391 | node_test: { 392 | id: 1, 393 | node: { 394 | id: '5362389af1e6696e0395864e', 395 | label: '2', 396 | attributes: { 397 | Gender: 'Female', 398 | Position: 'Graduate', 399 | list: 'Respondent', 400 | name: 'Cleopatra Cordray', 401 | status: 'Submitted' 402 | } 403 | } 404 | }, 405 | edges_nb: 20, 406 | edge_test: { 407 | id: 3, 408 | edge: { 409 | id: '4', 410 | label: '', 411 | attributes: { 412 | Q1: 'true' 413 | }, 414 | source: '5362389af1e6696e0395864e', 415 | target: '5362389af1e6696e03958654', 416 | type: 'directed', 417 | weight: 1 418 | } 419 | } 420 | } 421 | }, 422 | { 423 | title: 'ListString Graph', 424 | gexf: 'liststring', 425 | basics: { 426 | version: '1.2', 427 | mode: 'static', 428 | defaultEdgeType: 'directed', 429 | meta: { 430 | creator: 'Gephi.org', 431 | description: 'A Web network', 432 | lastmodifieddate: '2009-03-20' 433 | }, 434 | model: [ 435 | {id: '0', title: 'types', type: 'liststring'}, 436 | {id: '1', title: 'indegree', type: 'float'}, 437 | {id: '2', title: 'frog', type: 'boolean', defaultValue: 'true'} 438 | ], 439 | nodes_nb: 4, 440 | node_test: { 441 | id: 0, 442 | node: { 443 | id: '0', 444 | label: 'Gephi', 445 | attributes: { 446 | '0': ['cooking', 'money'], 447 | '1': 1, 448 | '2': true 449 | } 450 | } 451 | }, 452 | edges_nb: 5, 453 | edge_test: { 454 | id: 3, 455 | edge: { 456 | id: '3', 457 | label: '', 458 | source: '2', 459 | target: '1', 460 | type: 'directed', 461 | weight: 1 462 | } 463 | } 464 | } 465 | } 466 | ]; 467 | 468 | // Most standard cases 469 | it('should be able to handle every standard cases.', function(done) { 470 | 471 | // Testing every gexf file 472 | async.parallel( 473 | tests.map(function(t, i) { 474 | return function(next) { 475 | 476 | helpers.fetch(t.gexf, function(graph) { 477 | var basics = tests[i].basics; 478 | 479 | // Root information 480 | assert.strictEqual(graph.version, basics.version, 'Version is retrieved.'); 481 | assert.strictEqual(graph.mode, basics.mode, 'Mode is retrieved.'); 482 | assert.strictEqual( 483 | graph.defaultEdgeType, 484 | basics.defaultEdgeType, 485 | 'DefaultEdgeType is retrieved.' 486 | ); 487 | 488 | // Meta 489 | assert.deepEqual( 490 | graph.meta, 491 | basics.meta, 492 | 'Meta information is retrieved.' 493 | ); 494 | 495 | // Node Model 496 | assert.deepEqual( 497 | graph.model.node, 498 | basics.model, 499 | 'Node model correctly retrieved.' 500 | ); 501 | 502 | // Edge Model 503 | assert.deepEqual( 504 | graph.model.edge, 505 | basics.edgeModel, 506 | 'Edge model correctly retrieved.' 507 | ); 508 | 509 | // Nodes 510 | assert.strictEqual(graph.nodes.length, basics.nodes_nb, 'All nodes retrieved.'); 511 | assert.deepEqual( 512 | graph.nodes[basics.node_test.id], 513 | basics.node_test.node, 514 | 'Node test passed.' 515 | ); 516 | 517 | // Edges 518 | assert.strictEqual(graph.edges.length, basics.edges_nb, 'All edges retrieved.'); 519 | assert.deepEqual( 520 | graph.edges[basics.edge_test.id], 521 | basics.edge_test.edge, 522 | 'Edge test passed.' 523 | ); 524 | 525 | next(); 526 | }); 527 | } 528 | }), 529 | done 530 | ); 531 | }); 532 | }); 533 | -------------------------------------------------------------------------------- /src/parser.js: -------------------------------------------------------------------------------- 1 | ;(function(undefined) { 2 | 'use strict'; 3 | 4 | /** 5 | * GEXF Parser 6 | * ============ 7 | * 8 | * Author: PLIQUE Guillaume (Yomguithereal) 9 | * URL: https://github.com/Yomguithereal/gexf 10 | * Version: 0.2.5 11 | */ 12 | 13 | /** 14 | * Helper Namespace 15 | * ----------------- 16 | * 17 | * A useful batch of function dealing with DOM operations and types. 18 | */ 19 | var _helpers = { 20 | getModelTags: function(xml) { 21 | var attributesTags = xml.getElementsByTagName('attributes'), 22 | modelTags = {}, 23 | l = attributesTags.length, 24 | i; 25 | 26 | for (i = 0; i < l; i++) 27 | modelTags[attributesTags[i].getAttribute('class')] = 28 | attributesTags[i].childNodes; 29 | 30 | return modelTags; 31 | }, 32 | nodeListToArray: function(nodeList) { 33 | 34 | // Return array 35 | var children = []; 36 | 37 | // Iterating 38 | for (var i = 0, len = nodeList.length; i < len; ++i) { 39 | if (nodeList[i].nodeName !== '#text') 40 | children.push(nodeList[i]); 41 | } 42 | 43 | return children; 44 | }, 45 | nodeListEach: function(nodeList, func) { 46 | 47 | // Iterating 48 | for (var i = 0, len = nodeList.length; i < len; ++i) { 49 | if (nodeList[i].nodeName !== '#text') 50 | func(nodeList[i]); 51 | } 52 | }, 53 | nodeListToHash: function(nodeList, filter) { 54 | 55 | // Return object 56 | var children = {}; 57 | 58 | // Iterating 59 | for (var i = 0; i < nodeList.length; i++) { 60 | if (nodeList[i].nodeName !== '#text') { 61 | var prop = filter(nodeList[i]); 62 | children[prop.key] = prop.value; 63 | } 64 | } 65 | 66 | return children; 67 | }, 68 | namedNodeMapToObject: function(nodeMap) { 69 | 70 | // Return object 71 | var attributes = {}; 72 | 73 | // Iterating 74 | for (var i = 0; i < nodeMap.length; i++) { 75 | attributes[nodeMap[i].name] = nodeMap[i].value; 76 | } 77 | 78 | return attributes; 79 | }, 80 | getFirstElementByTagNS: function(node, ns, tag) { 81 | var el = node.getElementsByTagName(ns + ':' + tag)[0]; 82 | 83 | if (!el) 84 | el = node.getElementsByTagNameNS(ns, tag)[0]; 85 | 86 | if (!el) 87 | el = node.getElementsByTagName(tag)[0]; 88 | 89 | return el; 90 | }, 91 | getAttributeNS: function(node, ns, attribute) { 92 | var attr_value = node.getAttribute(ns + ':' + attribute); 93 | 94 | if (attr_value === undefined) 95 | attr_value = node.getAttributeNS(ns, attribute); 96 | 97 | if (attr_value === undefined) 98 | attr_value = node.getAttribute(attribute); 99 | 100 | return attr_value; 101 | }, 102 | enforceType: function(type, value) { 103 | 104 | switch (type) { 105 | case 'boolean': 106 | value = (value === 'true'); 107 | break; 108 | 109 | case 'integer': 110 | case 'long': 111 | case 'float': 112 | case 'double': 113 | value = +value; 114 | break; 115 | 116 | case 'liststring': 117 | value = value ? value.split('|') : []; 118 | break; 119 | } 120 | 121 | return value; 122 | }, 123 | getRGB: function(values) { 124 | return (values[3]) ? 125 | 'rgba(' + values.join(',') + ')' : 126 | 'rgb(' + values.slice(0, -1).join(',') + ')'; 127 | } 128 | }; 129 | 130 | 131 | /** 132 | * Parser Core Functions 133 | * ---------------------- 134 | * 135 | * The XML parser's functions themselves. 136 | */ 137 | 138 | /** 139 | * Node structure. 140 | * A function returning an object guarded with default value. 141 | * 142 | * @param {object} properties The node properties. 143 | * @return {object} The guarded node object. 144 | */ 145 | function Node(properties) { 146 | 147 | // Possible Properties 148 | var node = { 149 | id: properties.id, 150 | label: properties.label 151 | }; 152 | 153 | if (properties.viz) 154 | node.viz = properties.viz; 155 | 156 | if (properties.attributes) 157 | node.attributes = properties.attributes; 158 | 159 | return node; 160 | } 161 | 162 | 163 | /** 164 | * Edge structure. 165 | * A function returning an object guarded with default value. 166 | * 167 | * @param {object} properties The edge properties. 168 | * @return {object} The guarded edge object. 169 | */ 170 | function Edge(properties) { 171 | 172 | // Possible Properties 173 | var edge = { 174 | id: properties.id, 175 | type: properties.type || 'undirected', 176 | label: properties.label || '', 177 | source: properties.source, 178 | target: properties.target, 179 | weight: +properties.weight || 1.0 180 | }; 181 | 182 | if (properties.viz) 183 | edge.viz = properties.viz; 184 | 185 | if (properties.attributes) 186 | edge.attributes = properties.attributes; 187 | 188 | return edge; 189 | } 190 | 191 | /** 192 | * Graph parser. 193 | * This structure parse a gexf string and return an object containing the 194 | * parsed graph. 195 | * 196 | * @param {string} xml The xml string of the gexf file to parse. 197 | * @return {object} The parsed graph. 198 | */ 199 | function Graph(xml) { 200 | var _xml = {}; 201 | 202 | // Basic Properties 203 | //------------------ 204 | _xml.els = { 205 | root: xml.getElementsByTagName('gexf')[0], 206 | graph: xml.getElementsByTagName('graph')[0], 207 | meta: xml.getElementsByTagName('meta')[0], 208 | nodes: xml.getElementsByTagName('node'), 209 | edges: xml.getElementsByTagName('edge'), 210 | model: _helpers.getModelTags(xml) 211 | }; 212 | 213 | // Information 214 | _xml.hasViz = !!_helpers.getAttributeNS(_xml.els.root, 'xmlns', 'viz'); 215 | _xml.version = _xml.els.root.getAttribute('version') || '1.0'; 216 | _xml.mode = _xml.els.graph.getAttribute('mode') || 'static'; 217 | 218 | var edgeType = _xml.els.graph.getAttribute('defaultedgetype'); 219 | _xml.defaultEdgetype = edgeType || 'undirected'; 220 | 221 | // Parser Functions 222 | //------------------ 223 | 224 | // Meta Data 225 | function _metaData() { 226 | 227 | var metas = {}; 228 | if (!_xml.els.meta) 229 | return metas; 230 | 231 | // Last modified date 232 | metas.lastmodifieddate = _xml.els.meta.getAttribute('lastmodifieddate'); 233 | 234 | // Other information 235 | _helpers.nodeListEach(_xml.els.meta.childNodes, function(child) { 236 | metas[child.tagName.toLowerCase()] = child.textContent; 237 | }); 238 | 239 | return metas; 240 | } 241 | 242 | // Model 243 | function _model(cls) { 244 | var attributes = []; 245 | 246 | // Iterating through attributes 247 | if (_xml.els.model[cls]) 248 | _helpers.nodeListEach(_xml.els.model[cls], function(attr) { 249 | 250 | // Properties 251 | var properties = { 252 | id: attr.getAttribute('id') || attr.getAttribute('for'), 253 | type: attr.getAttribute('type') || 'string', 254 | title: attr.getAttribute('title') || '' 255 | }; 256 | 257 | // Defaults 258 | var default_el = _helpers.nodeListToArray(attr.childNodes); 259 | 260 | if (default_el.length > 0) 261 | properties.defaultValue = default_el[0].textContent; 262 | 263 | // Creating attribute 264 | attributes.push(properties); 265 | }); 266 | 267 | return attributes.length > 0 ? attributes : false; 268 | } 269 | 270 | // Data from nodes or edges 271 | function _data(model, node_or_edge) { 272 | 273 | var data = {}; 274 | var attvalues_els = node_or_edge.getElementsByTagName('attvalue'); 275 | 276 | // Getting Node Indicated Attributes 277 | var ah = _helpers.nodeListToHash(attvalues_els, function(el) { 278 | var attributes = _helpers.namedNodeMapToObject(el.attributes); 279 | var key = attributes.id || attributes['for']; 280 | 281 | // Returning object 282 | return {key: key, value: attributes.value}; 283 | }); 284 | 285 | 286 | // Iterating through model 287 | model.map(function(a) { 288 | 289 | // Default value? 290 | data[a.id] = !(a.id in ah) && 'defaultValue' in a ? 291 | _helpers.enforceType(a.type, a.defaultValue) : 292 | _helpers.enforceType(a.type, ah[a.id]); 293 | 294 | }); 295 | 296 | return data; 297 | } 298 | 299 | // Nodes 300 | function _nodes(model) { 301 | var nodes = []; 302 | 303 | // Iteration through nodes 304 | _helpers.nodeListEach(_xml.els.nodes, function(n) { 305 | 306 | // Basic properties 307 | var properties = { 308 | id: n.getAttribute('id'), 309 | label: n.getAttribute('label') || '' 310 | }; 311 | 312 | // Retrieving data from nodes if any 313 | if (model) 314 | properties.attributes = _data(model, n); 315 | 316 | // Retrieving viz information 317 | if (_xml.hasViz) 318 | properties.viz = _nodeViz(n); 319 | 320 | // Pushing node 321 | nodes.push(Node(properties)); 322 | }); 323 | 324 | return nodes; 325 | } 326 | 327 | // Viz information from nodes 328 | function _nodeViz(node) { 329 | var viz = {}; 330 | 331 | // Color 332 | var color_el = _helpers.getFirstElementByTagNS(node, 'viz', 'color'); 333 | 334 | if (color_el) { 335 | var color = ['r', 'g', 'b', 'a'].map(function(c) { 336 | return color_el.getAttribute(c); 337 | }); 338 | 339 | viz.color = _helpers.getRGB(color); 340 | } 341 | 342 | // Position 343 | var pos_el = _helpers.getFirstElementByTagNS(node, 'viz', 'position'); 344 | 345 | if (pos_el) { 346 | viz.position = {}; 347 | 348 | ['x', 'y', 'z'].map(function(p) { 349 | viz.position[p] = +pos_el.getAttribute(p); 350 | }); 351 | } 352 | 353 | // Size 354 | var size_el = _helpers.getFirstElementByTagNS(node, 'viz', 'size'); 355 | if (size_el) 356 | viz.size = +size_el.getAttribute('value'); 357 | 358 | // Shape 359 | var shape_el = _helpers.getFirstElementByTagNS(node, 'viz', 'shape'); 360 | if (shape_el) 361 | viz.shape = shape_el.getAttribute('value'); 362 | 363 | return viz; 364 | } 365 | 366 | // Edges 367 | function _edges(model, default_type) { 368 | var edges = []; 369 | 370 | // Iteration through edges 371 | _helpers.nodeListEach(_xml.els.edges, function(e) { 372 | 373 | // Creating the edge 374 | var properties = _helpers.namedNodeMapToObject(e.attributes); 375 | if (!('type' in properties)) { 376 | properties.type = default_type; 377 | } 378 | 379 | // Retrieving edge data 380 | if (model) 381 | properties.attributes = _data(model, e); 382 | 383 | 384 | // Retrieving viz information 385 | if (_xml.hasViz) 386 | properties.viz = _edgeViz(e); 387 | 388 | edges.push(Edge(properties)); 389 | }); 390 | 391 | return edges; 392 | } 393 | 394 | // Viz information from edges 395 | function _edgeViz(edge) { 396 | var viz = {}; 397 | 398 | // Color 399 | var color_el = _helpers.getFirstElementByTagNS(edge, 'viz', 'color'); 400 | 401 | if (color_el) { 402 | var color = ['r', 'g', 'b', 'a'].map(function(c) { 403 | return color_el.getAttribute(c); 404 | }); 405 | 406 | viz.color = _helpers.getRGB(color); 407 | } 408 | 409 | // Shape 410 | var shape_el = _helpers.getFirstElementByTagNS(edge, 'viz', 'shape'); 411 | if (shape_el) 412 | viz.shape = shape_el.getAttribute('value'); 413 | 414 | // Thickness 415 | var thick_el = _helpers.getFirstElementByTagNS(edge, 'viz', 'thickness'); 416 | if (thick_el) 417 | viz.thickness = +thick_el.getAttribute('value'); 418 | 419 | return viz; 420 | } 421 | 422 | 423 | // Returning the Graph 424 | //--------------------- 425 | var nodeModel = _model('node'), 426 | edgeModel = _model('edge'); 427 | 428 | var graph = { 429 | version: _xml.version, 430 | mode: _xml.mode, 431 | defaultEdgeType: _xml.defaultEdgetype, 432 | meta: _metaData(), 433 | model: {}, 434 | nodes: _nodes(nodeModel), 435 | edges: _edges(edgeModel, _xml.defaultEdgetype) 436 | }; 437 | 438 | if (nodeModel) 439 | graph.model.node = nodeModel; 440 | if (edgeModel) 441 | graph.model.edge = edgeModel; 442 | 443 | return graph; 444 | } 445 | 446 | 447 | /** 448 | * Public API 449 | * ----------- 450 | * 451 | * User-accessible functions. 452 | */ 453 | 454 | // Fetching GEXF with XHR 455 | function fetch(gexf_url, callback) { 456 | var xhr = (function() { 457 | if (window.XMLHttpRequest) 458 | return new XMLHttpRequest(); 459 | 460 | var names, 461 | i; 462 | 463 | if (window.ActiveXObject) { 464 | names = [ 465 | 'Msxml2.XMLHTTP.6.0', 466 | 'Msxml2.XMLHTTP.3.0', 467 | 'Msxml2.XMLHTTP', 468 | 'Microsoft.XMLHTTP' 469 | ]; 470 | 471 | for (i in names) 472 | try { 473 | return new ActiveXObject(names[i]); 474 | } catch (e) {} 475 | } 476 | 477 | return null; 478 | })(); 479 | 480 | if (!xhr) 481 | throw 'XMLHttpRequest not supported, cannot load the file.'; 482 | 483 | // Async? 484 | var async = (typeof callback === 'function'), 485 | getResult; 486 | 487 | // If we can't override MIME type, we are on IE 9 488 | // We'll be parsing the response string then. 489 | if (xhr.overrideMimeType) { 490 | xhr.overrideMimeType('text/xml'); 491 | getResult = function(r) { 492 | return r.responseXML; 493 | }; 494 | } 495 | else { 496 | getResult = function(r) { 497 | var p = new DOMParser(); 498 | return p.parseFromString(r.responseText, 'application/xml'); 499 | }; 500 | } 501 | 502 | xhr.open('GET', gexf_url, async); 503 | 504 | if (async) 505 | xhr.onreadystatechange = function() { 506 | if (xhr.readyState === 4) 507 | callback(getResult(xhr)); 508 | }; 509 | 510 | xhr.send(); 511 | 512 | return (async) ? xhr : getResult(xhr); 513 | } 514 | 515 | // Parsing the GEXF File 516 | function parse(gexf) { 517 | return Graph(gexf); 518 | } 519 | 520 | // Fetch and parse the GEXF File 521 | function fetchAndParse(gexf_url, callback) { 522 | if (typeof callback === 'function') { 523 | return fetch(gexf_url, function(gexf) { 524 | callback(Graph(gexf)); 525 | }); 526 | } else 527 | return Graph(fetch(gexf_url)); 528 | } 529 | 530 | 531 | /** 532 | * Exporting 533 | * ---------- 534 | */ 535 | var gexf = { 536 | 537 | // Functions 538 | parse: parse, 539 | fetch: fetchAndParse, 540 | 541 | // Version 542 | version: '0.2.5' 543 | }; 544 | 545 | if (typeof exports !== 'undefined') { 546 | if (typeof module !== 'undefined' && module.exports) 547 | exports = module.exports = gexf; 548 | exports.gexf = gexf; 549 | } 550 | else if (typeof define === 'function' && define.amd) { 551 | define('gexf', [], function() { 552 | return gexf; 553 | }); 554 | } 555 | else { 556 | 557 | if (typeof this.gexf !== 'undefined') { 558 | 559 | // Extending 560 | this.gexf.parse = parse; 561 | this.gexf.fetch = fetchAndParse; 562 | } 563 | else { 564 | 565 | // Creating 566 | this.gexf = gexf; 567 | } 568 | } 569 | }).call(this); 570 | -------------------------------------------------------------------------------- /test/browser/async.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * async 3 | * https://github.com/caolan/async 4 | * 5 | * Copyright 2010-2014 Caolan McMahon 6 | * Released under the MIT license 7 | */ 8 | /*jshint onevar: false, indent:4 */ 9 | /*global setImmediate: false, setTimeout: false, console: false */ 10 | (function () { 11 | 12 | var async = {}; 13 | 14 | // global on the server, window in the browser 15 | var root, previous_async; 16 | 17 | root = this; 18 | if (root != null) { 19 | previous_async = root.async; 20 | } 21 | 22 | async.noConflict = function () { 23 | root.async = previous_async; 24 | return async; 25 | }; 26 | 27 | function only_once(fn) { 28 | var called = false; 29 | return function() { 30 | if (called) throw new Error("Callback was already called."); 31 | called = true; 32 | fn.apply(root, arguments); 33 | } 34 | } 35 | 36 | //// cross-browser compatiblity functions //// 37 | 38 | var _toString = Object.prototype.toString; 39 | 40 | var _isArray = Array.isArray || function (obj) { 41 | return _toString.call(obj) === '[object Array]'; 42 | }; 43 | 44 | var _each = function (arr, iterator) { 45 | if (arr.forEach) { 46 | return arr.forEach(iterator); 47 | } 48 | for (var i = 0; i < arr.length; i += 1) { 49 | iterator(arr[i], i, arr); 50 | } 51 | }; 52 | 53 | var _map = function (arr, iterator) { 54 | if (arr.map) { 55 | return arr.map(iterator); 56 | } 57 | var results = []; 58 | _each(arr, function (x, i, a) { 59 | results.push(iterator(x, i, a)); 60 | }); 61 | return results; 62 | }; 63 | 64 | var _reduce = function (arr, iterator, memo) { 65 | if (arr.reduce) { 66 | return arr.reduce(iterator, memo); 67 | } 68 | _each(arr, function (x, i, a) { 69 | memo = iterator(memo, x, i, a); 70 | }); 71 | return memo; 72 | }; 73 | 74 | var _keys = function (obj) { 75 | if (Object.keys) { 76 | return Object.keys(obj); 77 | } 78 | var keys = []; 79 | for (var k in obj) { 80 | if (obj.hasOwnProperty(k)) { 81 | keys.push(k); 82 | } 83 | } 84 | return keys; 85 | }; 86 | 87 | //// exported async module functions //// 88 | 89 | //// nextTick implementation with browser-compatible fallback //// 90 | if (typeof process === 'undefined' || !(process.nextTick)) { 91 | if (typeof setImmediate === 'function') { 92 | async.nextTick = function (fn) { 93 | // not a direct alias for IE10 compatibility 94 | setImmediate(fn); 95 | }; 96 | async.setImmediate = async.nextTick; 97 | } 98 | else { 99 | async.nextTick = function (fn) { 100 | setTimeout(fn, 0); 101 | }; 102 | async.setImmediate = async.nextTick; 103 | } 104 | } 105 | else { 106 | async.nextTick = process.nextTick; 107 | if (typeof setImmediate !== 'undefined') { 108 | async.setImmediate = function (fn) { 109 | // not a direct alias for IE10 compatibility 110 | setImmediate(fn); 111 | }; 112 | } 113 | else { 114 | async.setImmediate = async.nextTick; 115 | } 116 | } 117 | 118 | async.each = function (arr, iterator, callback) { 119 | callback = callback || function () {}; 120 | if (!arr.length) { 121 | return callback(); 122 | } 123 | var completed = 0; 124 | _each(arr, function (x) { 125 | iterator(x, only_once(done) ); 126 | }); 127 | function done(err) { 128 | if (err) { 129 | callback(err); 130 | callback = function () {}; 131 | } 132 | else { 133 | completed += 1; 134 | if (completed >= arr.length) { 135 | callback(); 136 | } 137 | } 138 | } 139 | }; 140 | async.forEach = async.each; 141 | 142 | async.eachSeries = function (arr, iterator, callback) { 143 | callback = callback || function () {}; 144 | if (!arr.length) { 145 | return callback(); 146 | } 147 | var completed = 0; 148 | var iterate = function () { 149 | iterator(arr[completed], function (err) { 150 | if (err) { 151 | callback(err); 152 | callback = function () {}; 153 | } 154 | else { 155 | completed += 1; 156 | if (completed >= arr.length) { 157 | callback(); 158 | } 159 | else { 160 | iterate(); 161 | } 162 | } 163 | }); 164 | }; 165 | iterate(); 166 | }; 167 | async.forEachSeries = async.eachSeries; 168 | 169 | async.eachLimit = function (arr, limit, iterator, callback) { 170 | var fn = _eachLimit(limit); 171 | fn.apply(null, [arr, iterator, callback]); 172 | }; 173 | async.forEachLimit = async.eachLimit; 174 | 175 | var _eachLimit = function (limit) { 176 | 177 | return function (arr, iterator, callback) { 178 | callback = callback || function () {}; 179 | if (!arr.length || limit <= 0) { 180 | return callback(); 181 | } 182 | var completed = 0; 183 | var started = 0; 184 | var running = 0; 185 | 186 | (function replenish () { 187 | if (completed >= arr.length) { 188 | return callback(); 189 | } 190 | 191 | while (running < limit && started < arr.length) { 192 | started += 1; 193 | running += 1; 194 | iterator(arr[started - 1], function (err) { 195 | if (err) { 196 | callback(err); 197 | callback = function () {}; 198 | } 199 | else { 200 | completed += 1; 201 | running -= 1; 202 | if (completed >= arr.length) { 203 | callback(); 204 | } 205 | else { 206 | replenish(); 207 | } 208 | } 209 | }); 210 | } 211 | })(); 212 | }; 213 | }; 214 | 215 | 216 | var doParallel = function (fn) { 217 | return function () { 218 | var args = Array.prototype.slice.call(arguments); 219 | return fn.apply(null, [async.each].concat(args)); 220 | }; 221 | }; 222 | var doParallelLimit = function(limit, fn) { 223 | return function () { 224 | var args = Array.prototype.slice.call(arguments); 225 | return fn.apply(null, [_eachLimit(limit)].concat(args)); 226 | }; 227 | }; 228 | var doSeries = function (fn) { 229 | return function () { 230 | var args = Array.prototype.slice.call(arguments); 231 | return fn.apply(null, [async.eachSeries].concat(args)); 232 | }; 233 | }; 234 | 235 | 236 | var _asyncMap = function (eachfn, arr, iterator, callback) { 237 | arr = _map(arr, function (x, i) { 238 | return {index: i, value: x}; 239 | }); 240 | if (!callback) { 241 | eachfn(arr, function (x, callback) { 242 | iterator(x.value, function (err) { 243 | callback(err); 244 | }); 245 | }); 246 | } else { 247 | var results = []; 248 | eachfn(arr, function (x, callback) { 249 | iterator(x.value, function (err, v) { 250 | results[x.index] = v; 251 | callback(err); 252 | }); 253 | }, function (err) { 254 | callback(err, results); 255 | }); 256 | } 257 | }; 258 | async.map = doParallel(_asyncMap); 259 | async.mapSeries = doSeries(_asyncMap); 260 | async.mapLimit = function (arr, limit, iterator, callback) { 261 | return _mapLimit(limit)(arr, iterator, callback); 262 | }; 263 | 264 | var _mapLimit = function(limit) { 265 | return doParallelLimit(limit, _asyncMap); 266 | }; 267 | 268 | // reduce only has a series version, as doing reduce in parallel won't 269 | // work in many situations. 270 | async.reduce = function (arr, memo, iterator, callback) { 271 | async.eachSeries(arr, function (x, callback) { 272 | iterator(memo, x, function (err, v) { 273 | memo = v; 274 | callback(err); 275 | }); 276 | }, function (err) { 277 | callback(err, memo); 278 | }); 279 | }; 280 | // inject alias 281 | async.inject = async.reduce; 282 | // foldl alias 283 | async.foldl = async.reduce; 284 | 285 | async.reduceRight = function (arr, memo, iterator, callback) { 286 | var reversed = _map(arr, function (x) { 287 | return x; 288 | }).reverse(); 289 | async.reduce(reversed, memo, iterator, callback); 290 | }; 291 | // foldr alias 292 | async.foldr = async.reduceRight; 293 | 294 | var _filter = function (eachfn, arr, iterator, callback) { 295 | var results = []; 296 | arr = _map(arr, function (x, i) { 297 | return {index: i, value: x}; 298 | }); 299 | eachfn(arr, function (x, callback) { 300 | iterator(x.value, function (v) { 301 | if (v) { 302 | results.push(x); 303 | } 304 | callback(); 305 | }); 306 | }, function (err) { 307 | callback(_map(results.sort(function (a, b) { 308 | return a.index - b.index; 309 | }), function (x) { 310 | return x.value; 311 | })); 312 | }); 313 | }; 314 | async.filter = doParallel(_filter); 315 | async.filterSeries = doSeries(_filter); 316 | // select alias 317 | async.select = async.filter; 318 | async.selectSeries = async.filterSeries; 319 | 320 | var _reject = function (eachfn, arr, iterator, callback) { 321 | var results = []; 322 | arr = _map(arr, function (x, i) { 323 | return {index: i, value: x}; 324 | }); 325 | eachfn(arr, function (x, callback) { 326 | iterator(x.value, function (v) { 327 | if (!v) { 328 | results.push(x); 329 | } 330 | callback(); 331 | }); 332 | }, function (err) { 333 | callback(_map(results.sort(function (a, b) { 334 | return a.index - b.index; 335 | }), function (x) { 336 | return x.value; 337 | })); 338 | }); 339 | }; 340 | async.reject = doParallel(_reject); 341 | async.rejectSeries = doSeries(_reject); 342 | 343 | var _detect = function (eachfn, arr, iterator, main_callback) { 344 | eachfn(arr, function (x, callback) { 345 | iterator(x, function (result) { 346 | if (result) { 347 | main_callback(x); 348 | main_callback = function () {}; 349 | } 350 | else { 351 | callback(); 352 | } 353 | }); 354 | }, function (err) { 355 | main_callback(); 356 | }); 357 | }; 358 | async.detect = doParallel(_detect); 359 | async.detectSeries = doSeries(_detect); 360 | 361 | async.some = function (arr, iterator, main_callback) { 362 | async.each(arr, function (x, callback) { 363 | iterator(x, function (v) { 364 | if (v) { 365 | main_callback(true); 366 | main_callback = function () {}; 367 | } 368 | callback(); 369 | }); 370 | }, function (err) { 371 | main_callback(false); 372 | }); 373 | }; 374 | // any alias 375 | async.any = async.some; 376 | 377 | async.every = function (arr, iterator, main_callback) { 378 | async.each(arr, function (x, callback) { 379 | iterator(x, function (v) { 380 | if (!v) { 381 | main_callback(false); 382 | main_callback = function () {}; 383 | } 384 | callback(); 385 | }); 386 | }, function (err) { 387 | main_callback(true); 388 | }); 389 | }; 390 | // all alias 391 | async.all = async.every; 392 | 393 | async.sortBy = function (arr, iterator, callback) { 394 | async.map(arr, function (x, callback) { 395 | iterator(x, function (err, criteria) { 396 | if (err) { 397 | callback(err); 398 | } 399 | else { 400 | callback(null, {value: x, criteria: criteria}); 401 | } 402 | }); 403 | }, function (err, results) { 404 | if (err) { 405 | return callback(err); 406 | } 407 | else { 408 | var fn = function (left, right) { 409 | var a = left.criteria, b = right.criteria; 410 | return a < b ? -1 : a > b ? 1 : 0; 411 | }; 412 | callback(null, _map(results.sort(fn), function (x) { 413 | return x.value; 414 | })); 415 | } 416 | }); 417 | }; 418 | 419 | async.auto = function (tasks, callback) { 420 | callback = callback || function () {}; 421 | var keys = _keys(tasks); 422 | var remainingTasks = keys.length 423 | if (!remainingTasks) { 424 | return callback(); 425 | } 426 | 427 | var results = {}; 428 | 429 | var listeners = []; 430 | var addListener = function (fn) { 431 | listeners.unshift(fn); 432 | }; 433 | var removeListener = function (fn) { 434 | for (var i = 0; i < listeners.length; i += 1) { 435 | if (listeners[i] === fn) { 436 | listeners.splice(i, 1); 437 | return; 438 | } 439 | } 440 | }; 441 | var taskComplete = function () { 442 | remainingTasks-- 443 | _each(listeners.slice(0), function (fn) { 444 | fn(); 445 | }); 446 | }; 447 | 448 | addListener(function () { 449 | if (!remainingTasks) { 450 | var theCallback = callback; 451 | // prevent final callback from calling itself if it errors 452 | callback = function () {}; 453 | 454 | theCallback(null, results); 455 | } 456 | }); 457 | 458 | _each(keys, function (k) { 459 | var task = _isArray(tasks[k]) ? tasks[k]: [tasks[k]]; 460 | var taskCallback = function (err) { 461 | var args = Array.prototype.slice.call(arguments, 1); 462 | if (args.length <= 1) { 463 | args = args[0]; 464 | } 465 | if (err) { 466 | var safeResults = {}; 467 | _each(_keys(results), function(rkey) { 468 | safeResults[rkey] = results[rkey]; 469 | }); 470 | safeResults[k] = args; 471 | callback(err, safeResults); 472 | // stop subsequent errors hitting callback multiple times 473 | callback = function () {}; 474 | } 475 | else { 476 | results[k] = args; 477 | async.setImmediate(taskComplete); 478 | } 479 | }; 480 | var requires = task.slice(0, Math.abs(task.length - 1)) || []; 481 | var ready = function () { 482 | return _reduce(requires, function (a, x) { 483 | return (a && results.hasOwnProperty(x)); 484 | }, true) && !results.hasOwnProperty(k); 485 | }; 486 | if (ready()) { 487 | task[task.length - 1](taskCallback, results); 488 | } 489 | else { 490 | var listener = function () { 491 | if (ready()) { 492 | removeListener(listener); 493 | task[task.length - 1](taskCallback, results); 494 | } 495 | }; 496 | addListener(listener); 497 | } 498 | }); 499 | }; 500 | 501 | async.retry = function(times, task, callback) { 502 | var DEFAULT_TIMES = 5; 503 | var attempts = []; 504 | // Use defaults if times not passed 505 | if (typeof times === 'function') { 506 | callback = task; 507 | task = times; 508 | times = DEFAULT_TIMES; 509 | } 510 | // Make sure times is a number 511 | times = parseInt(times, 10) || DEFAULT_TIMES; 512 | var wrappedTask = function(wrappedCallback, wrappedResults) { 513 | var retryAttempt = function(task, finalAttempt) { 514 | return function(seriesCallback) { 515 | task(function(err, result){ 516 | seriesCallback(!err || finalAttempt, {err: err, result: result}); 517 | }, wrappedResults); 518 | }; 519 | }; 520 | while (times) { 521 | attempts.push(retryAttempt(task, !(times-=1))); 522 | } 523 | async.series(attempts, function(done, data){ 524 | data = data[data.length - 1]; 525 | (wrappedCallback || callback)(data.err, data.result); 526 | }); 527 | } 528 | // If a callback is passed, run this as a controll flow 529 | return callback ? wrappedTask() : wrappedTask 530 | }; 531 | 532 | async.waterfall = function (tasks, callback) { 533 | callback = callback || function () {}; 534 | if (!_isArray(tasks)) { 535 | var err = new Error('First argument to waterfall must be an array of functions'); 536 | return callback(err); 537 | } 538 | if (!tasks.length) { 539 | return callback(); 540 | } 541 | var wrapIterator = function (iterator) { 542 | return function (err) { 543 | if (err) { 544 | callback.apply(null, arguments); 545 | callback = function () {}; 546 | } 547 | else { 548 | var args = Array.prototype.slice.call(arguments, 1); 549 | var next = iterator.next(); 550 | if (next) { 551 | args.push(wrapIterator(next)); 552 | } 553 | else { 554 | args.push(callback); 555 | } 556 | async.setImmediate(function () { 557 | iterator.apply(null, args); 558 | }); 559 | } 560 | }; 561 | }; 562 | wrapIterator(async.iterator(tasks))(); 563 | }; 564 | 565 | var _parallel = function(eachfn, tasks, callback) { 566 | callback = callback || function () {}; 567 | if (_isArray(tasks)) { 568 | eachfn.map(tasks, function (fn, callback) { 569 | if (fn) { 570 | fn(function (err) { 571 | var args = Array.prototype.slice.call(arguments, 1); 572 | if (args.length <= 1) { 573 | args = args[0]; 574 | } 575 | callback.call(null, err, args); 576 | }); 577 | } 578 | }, callback); 579 | } 580 | else { 581 | var results = {}; 582 | eachfn.each(_keys(tasks), function (k, callback) { 583 | tasks[k](function (err) { 584 | var args = Array.prototype.slice.call(arguments, 1); 585 | if (args.length <= 1) { 586 | args = args[0]; 587 | } 588 | results[k] = args; 589 | callback(err); 590 | }); 591 | }, function (err) { 592 | callback(err, results); 593 | }); 594 | } 595 | }; 596 | 597 | async.parallel = function (tasks, callback) { 598 | _parallel({ map: async.map, each: async.each }, tasks, callback); 599 | }; 600 | 601 | async.parallelLimit = function(tasks, limit, callback) { 602 | _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback); 603 | }; 604 | 605 | async.series = function (tasks, callback) { 606 | callback = callback || function () {}; 607 | if (_isArray(tasks)) { 608 | async.mapSeries(tasks, function (fn, callback) { 609 | if (fn) { 610 | fn(function (err) { 611 | var args = Array.prototype.slice.call(arguments, 1); 612 | if (args.length <= 1) { 613 | args = args[0]; 614 | } 615 | callback.call(null, err, args); 616 | }); 617 | } 618 | }, callback); 619 | } 620 | else { 621 | var results = {}; 622 | async.eachSeries(_keys(tasks), function (k, callback) { 623 | tasks[k](function (err) { 624 | var args = Array.prototype.slice.call(arguments, 1); 625 | if (args.length <= 1) { 626 | args = args[0]; 627 | } 628 | results[k] = args; 629 | callback(err); 630 | }); 631 | }, function (err) { 632 | callback(err, results); 633 | }); 634 | } 635 | }; 636 | 637 | async.iterator = function (tasks) { 638 | var makeCallback = function (index) { 639 | var fn = function () { 640 | if (tasks.length) { 641 | tasks[index].apply(null, arguments); 642 | } 643 | return fn.next(); 644 | }; 645 | fn.next = function () { 646 | return (index < tasks.length - 1) ? makeCallback(index + 1): null; 647 | }; 648 | return fn; 649 | }; 650 | return makeCallback(0); 651 | }; 652 | 653 | async.apply = function (fn) { 654 | var args = Array.prototype.slice.call(arguments, 1); 655 | return function () { 656 | return fn.apply( 657 | null, args.concat(Array.prototype.slice.call(arguments)) 658 | ); 659 | }; 660 | }; 661 | 662 | var _concat = function (eachfn, arr, fn, callback) { 663 | var r = []; 664 | eachfn(arr, function (x, cb) { 665 | fn(x, function (err, y) { 666 | r = r.concat(y || []); 667 | cb(err); 668 | }); 669 | }, function (err) { 670 | callback(err, r); 671 | }); 672 | }; 673 | async.concat = doParallel(_concat); 674 | async.concatSeries = doSeries(_concat); 675 | 676 | async.whilst = function (test, iterator, callback) { 677 | if (test()) { 678 | iterator(function (err) { 679 | if (err) { 680 | return callback(err); 681 | } 682 | async.whilst(test, iterator, callback); 683 | }); 684 | } 685 | else { 686 | callback(); 687 | } 688 | }; 689 | 690 | async.doWhilst = function (iterator, test, callback) { 691 | iterator(function (err) { 692 | if (err) { 693 | return callback(err); 694 | } 695 | var args = Array.prototype.slice.call(arguments, 1); 696 | if (test.apply(null, args)) { 697 | async.doWhilst(iterator, test, callback); 698 | } 699 | else { 700 | callback(); 701 | } 702 | }); 703 | }; 704 | 705 | async.until = function (test, iterator, callback) { 706 | if (!test()) { 707 | iterator(function (err) { 708 | if (err) { 709 | return callback(err); 710 | } 711 | async.until(test, iterator, callback); 712 | }); 713 | } 714 | else { 715 | callback(); 716 | } 717 | }; 718 | 719 | async.doUntil = function (iterator, test, callback) { 720 | iterator(function (err) { 721 | if (err) { 722 | return callback(err); 723 | } 724 | var args = Array.prototype.slice.call(arguments, 1); 725 | if (!test.apply(null, args)) { 726 | async.doUntil(iterator, test, callback); 727 | } 728 | else { 729 | callback(); 730 | } 731 | }); 732 | }; 733 | 734 | async.queue = function (worker, concurrency) { 735 | if (concurrency === undefined) { 736 | concurrency = 1; 737 | } 738 | function _insert(q, data, pos, callback) { 739 | if (!q.started){ 740 | q.started = true; 741 | } 742 | if (!_isArray(data)) { 743 | data = [data]; 744 | } 745 | if(data.length == 0) { 746 | // call drain immediately if there are no tasks 747 | return async.setImmediate(function() { 748 | if (q.drain) { 749 | q.drain(); 750 | } 751 | }); 752 | } 753 | _each(data, function(task) { 754 | var item = { 755 | data: task, 756 | callback: typeof callback === 'function' ? callback : null 757 | }; 758 | 759 | if (pos) { 760 | q.tasks.unshift(item); 761 | } else { 762 | q.tasks.push(item); 763 | } 764 | 765 | if (q.saturated && q.tasks.length === q.concurrency) { 766 | q.saturated(); 767 | } 768 | async.setImmediate(q.process); 769 | }); 770 | } 771 | 772 | var workers = 0; 773 | var q = { 774 | tasks: [], 775 | concurrency: concurrency, 776 | saturated: null, 777 | empty: null, 778 | drain: null, 779 | started: false, 780 | paused: false, 781 | push: function (data, callback) { 782 | _insert(q, data, false, callback); 783 | }, 784 | kill: function () { 785 | q.drain = null; 786 | q.tasks = []; 787 | }, 788 | unshift: function (data, callback) { 789 | _insert(q, data, true, callback); 790 | }, 791 | process: function () { 792 | if (!q.paused && workers < q.concurrency && q.tasks.length) { 793 | var task = q.tasks.shift(); 794 | if (q.empty && q.tasks.length === 0) { 795 | q.empty(); 796 | } 797 | workers += 1; 798 | var next = function () { 799 | workers -= 1; 800 | if (task.callback) { 801 | task.callback.apply(task, arguments); 802 | } 803 | if (q.drain && q.tasks.length + workers === 0) { 804 | q.drain(); 805 | } 806 | q.process(); 807 | }; 808 | var cb = only_once(next); 809 | worker(task.data, cb); 810 | } 811 | }, 812 | length: function () { 813 | return q.tasks.length; 814 | }, 815 | running: function () { 816 | return workers; 817 | }, 818 | idle: function() { 819 | return q.tasks.length + workers === 0; 820 | }, 821 | pause: function () { 822 | if (q.paused === true) { return; } 823 | q.paused = true; 824 | }, 825 | resume: function () { 826 | if (q.paused === false) { return; } 827 | q.paused = false; 828 | // Need to call q.process once per concurrent 829 | // worker to preserve full concurrency after pause 830 | for (var w = 1; w <= q.concurrency; w++) { 831 | async.setImmediate(q.process); 832 | } 833 | } 834 | }; 835 | return q; 836 | }; 837 | 838 | async.priorityQueue = function (worker, concurrency) { 839 | 840 | function _compareTasks(a, b){ 841 | return a.priority - b.priority; 842 | }; 843 | 844 | function _binarySearch(sequence, item, compare) { 845 | var beg = -1, 846 | end = sequence.length - 1; 847 | while (beg < end) { 848 | var mid = beg + ((end - beg + 1) >>> 1); 849 | if (compare(item, sequence[mid]) >= 0) { 850 | beg = mid; 851 | } else { 852 | end = mid - 1; 853 | } 854 | } 855 | return beg; 856 | } 857 | 858 | function _insert(q, data, priority, callback) { 859 | if (!q.started){ 860 | q.started = true; 861 | } 862 | if (!_isArray(data)) { 863 | data = [data]; 864 | } 865 | if(data.length == 0) { 866 | // call drain immediately if there are no tasks 867 | return async.setImmediate(function() { 868 | if (q.drain) { 869 | q.drain(); 870 | } 871 | }); 872 | } 873 | _each(data, function(task) { 874 | var item = { 875 | data: task, 876 | priority: priority, 877 | callback: typeof callback === 'function' ? callback : null 878 | }; 879 | 880 | q.tasks.splice(_binarySearch(q.tasks, item, _compareTasks) + 1, 0, item); 881 | 882 | if (q.saturated && q.tasks.length === q.concurrency) { 883 | q.saturated(); 884 | } 885 | async.setImmediate(q.process); 886 | }); 887 | } 888 | 889 | // Start with a normal queue 890 | var q = async.queue(worker, concurrency); 891 | 892 | // Override push to accept second parameter representing priority 893 | q.push = function (data, priority, callback) { 894 | _insert(q, data, priority, callback); 895 | }; 896 | 897 | // Remove unshift function 898 | delete q.unshift; 899 | 900 | return q; 901 | }; 902 | 903 | async.cargo = function (worker, payload) { 904 | var working = false, 905 | tasks = []; 906 | 907 | var cargo = { 908 | tasks: tasks, 909 | payload: payload, 910 | saturated: null, 911 | empty: null, 912 | drain: null, 913 | drained: true, 914 | push: function (data, callback) { 915 | if (!_isArray(data)) { 916 | data = [data]; 917 | } 918 | _each(data, function(task) { 919 | tasks.push({ 920 | data: task, 921 | callback: typeof callback === 'function' ? callback : null 922 | }); 923 | cargo.drained = false; 924 | if (cargo.saturated && tasks.length === payload) { 925 | cargo.saturated(); 926 | } 927 | }); 928 | async.setImmediate(cargo.process); 929 | }, 930 | process: function process() { 931 | if (working) return; 932 | if (tasks.length === 0) { 933 | if(cargo.drain && !cargo.drained) cargo.drain(); 934 | cargo.drained = true; 935 | return; 936 | } 937 | 938 | var ts = typeof payload === 'number' 939 | ? tasks.splice(0, payload) 940 | : tasks.splice(0, tasks.length); 941 | 942 | var ds = _map(ts, function (task) { 943 | return task.data; 944 | }); 945 | 946 | if(cargo.empty) cargo.empty(); 947 | working = true; 948 | worker(ds, function () { 949 | working = false; 950 | 951 | var args = arguments; 952 | _each(ts, function (data) { 953 | if (data.callback) { 954 | data.callback.apply(null, args); 955 | } 956 | }); 957 | 958 | process(); 959 | }); 960 | }, 961 | length: function () { 962 | return tasks.length; 963 | }, 964 | running: function () { 965 | return working; 966 | } 967 | }; 968 | return cargo; 969 | }; 970 | 971 | var _console_fn = function (name) { 972 | return function (fn) { 973 | var args = Array.prototype.slice.call(arguments, 1); 974 | fn.apply(null, args.concat([function (err) { 975 | var args = Array.prototype.slice.call(arguments, 1); 976 | if (typeof console !== 'undefined') { 977 | if (err) { 978 | if (console.error) { 979 | console.error(err); 980 | } 981 | } 982 | else if (console[name]) { 983 | _each(args, function (x) { 984 | console[name](x); 985 | }); 986 | } 987 | } 988 | }])); 989 | }; 990 | }; 991 | async.log = _console_fn('log'); 992 | async.dir = _console_fn('dir'); 993 | /*async.info = _console_fn('info'); 994 | async.warn = _console_fn('warn'); 995 | async.error = _console_fn('error');*/ 996 | 997 | async.memoize = function (fn, hasher) { 998 | var memo = {}; 999 | var queues = {}; 1000 | hasher = hasher || function (x) { 1001 | return x; 1002 | }; 1003 | var memoized = function () { 1004 | var args = Array.prototype.slice.call(arguments); 1005 | var callback = args.pop(); 1006 | var key = hasher.apply(null, args); 1007 | if (key in memo) { 1008 | async.nextTick(function () { 1009 | callback.apply(null, memo[key]); 1010 | }); 1011 | } 1012 | else if (key in queues) { 1013 | queues[key].push(callback); 1014 | } 1015 | else { 1016 | queues[key] = [callback]; 1017 | fn.apply(null, args.concat([function () { 1018 | memo[key] = arguments; 1019 | var q = queues[key]; 1020 | delete queues[key]; 1021 | for (var i = 0, l = q.length; i < l; i++) { 1022 | q[i].apply(null, arguments); 1023 | } 1024 | }])); 1025 | } 1026 | }; 1027 | memoized.memo = memo; 1028 | memoized.unmemoized = fn; 1029 | return memoized; 1030 | }; 1031 | 1032 | async.unmemoize = function (fn) { 1033 | return function () { 1034 | return (fn.unmemoized || fn).apply(null, arguments); 1035 | }; 1036 | }; 1037 | 1038 | async.times = function (count, iterator, callback) { 1039 | var counter = []; 1040 | for (var i = 0; i < count; i++) { 1041 | counter.push(i); 1042 | } 1043 | return async.map(counter, iterator, callback); 1044 | }; 1045 | 1046 | async.timesSeries = function (count, iterator, callback) { 1047 | var counter = []; 1048 | for (var i = 0; i < count; i++) { 1049 | counter.push(i); 1050 | } 1051 | return async.mapSeries(counter, iterator, callback); 1052 | }; 1053 | 1054 | async.seq = function (/* functions... */) { 1055 | var fns = arguments; 1056 | return function () { 1057 | var that = this; 1058 | var args = Array.prototype.slice.call(arguments); 1059 | var callback = args.pop(); 1060 | async.reduce(fns, args, function (newargs, fn, cb) { 1061 | fn.apply(that, newargs.concat([function () { 1062 | var err = arguments[0]; 1063 | var nextargs = Array.prototype.slice.call(arguments, 1); 1064 | cb(err, nextargs); 1065 | }])) 1066 | }, 1067 | function (err, results) { 1068 | callback.apply(that, [err].concat(results)); 1069 | }); 1070 | }; 1071 | }; 1072 | 1073 | async.compose = function (/* functions... */) { 1074 | return async.seq.apply(null, Array.prototype.reverse.call(arguments)); 1075 | }; 1076 | 1077 | var _applyEach = function (eachfn, fns /*args...*/) { 1078 | var go = function () { 1079 | var that = this; 1080 | var args = Array.prototype.slice.call(arguments); 1081 | var callback = args.pop(); 1082 | return eachfn(fns, function (fn, cb) { 1083 | fn.apply(that, args.concat([cb])); 1084 | }, 1085 | callback); 1086 | }; 1087 | if (arguments.length > 2) { 1088 | var args = Array.prototype.slice.call(arguments, 2); 1089 | return go.apply(this, args); 1090 | } 1091 | else { 1092 | return go; 1093 | } 1094 | }; 1095 | async.applyEach = doParallel(_applyEach); 1096 | async.applyEachSeries = doSeries(_applyEach); 1097 | 1098 | async.forever = function (fn, callback) { 1099 | function next(err) { 1100 | if (err) { 1101 | if (callback) { 1102 | return callback(err); 1103 | } 1104 | throw err; 1105 | } 1106 | fn(next); 1107 | } 1108 | next(); 1109 | }; 1110 | 1111 | // Node.js 1112 | if (typeof module !== 'undefined' && module.exports) { 1113 | module.exports = async; 1114 | } 1115 | // AMD / RequireJS 1116 | else if (typeof define !== 'undefined' && define.amd) { 1117 | define([], function () { 1118 | return async; 1119 | }); 1120 | } 1121 | // included directly via