├── .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 | [](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