From 1eaf2ddfcc7b390bf2ef0c4c7e33ab47174117cd Mon Sep 17 00:00:00 2001 From: David Gschwend Date: Sun, 20 Mar 2016 13:49:44 +0100 Subject: [PATCH 01/77] updated Readme --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1f1184d..f38cf40 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ # Netscope -A web-based tool for visualizing neural network topologies. It currently supports UC Berkeley's [Caffe framework](https://github.com/bvlc/caffe). +This tool is based on the Netscope by [ethereon](https://github.com/ethereon). +Netscope is a web-based tool for visualizing neural network topologies. It currently supports UC Berkeley's [Caffe framework](https://github.com/bvlc/caffe). + +This fork adds analysis capabilities, enabling the computation of network complexity (number of operations) and network size (number of parameters) for easy comparison of different networks. ### Documentation -- [The Quick Start Guide](http://ethereon.github.io/netscope/quickstart.html) +- Netscope [Quick Start Guide](http://ethereon.github.io/netscope/quickstart.html) ### Demo - [Visualization of the Deep Convolutional Neural Network "AlexNet"](http://ethereon.github.io/netscope/#/preset/alexnet) From d8eb27dd535f79ef4c50896a939a3710a988cb20 Mon Sep 17 00:00:00 2001 From: David Gschwend Date: Sun, 20 Mar 2016 13:51:02 +0100 Subject: [PATCH 02/77] updated Readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f38cf40..1919716 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ Netscope is a web-based tool for visualizing neural network topologies. It curre This fork adds analysis capabilities, enabling the computation of network complexity (number of operations) and network size (number of parameters) for easy comparison of different networks. ### Documentation -- Netscope [Quick Start Guide](http://ethereon.github.io/netscope/quickstart.html) +- Netscope [Quick Start Guide](http://dgschwend.github.io/netscope/quickstart.html) ### Demo -- [Visualization of the Deep Convolutional Neural Network "AlexNet"](http://ethereon.github.io/netscope/#/preset/alexnet) +- [Visualization of the Deep Convolutional Neural Network "SqueezeNet"](http://dgschwend.github.io/netscope/#/preset/squeezenet) ### License From 1592e81d138a03b247897452b523fb2a75138c00 Mon Sep 17 00:00:00 2001 From: David Gschwend Date: Sun, 20 Mar 2016 13:58:37 +0100 Subject: [PATCH 03/77] Add Inception v3, SqueezeNet; Add dimensions to internal network/node list; Add dimensions to graph output; Add detailed table of network layers; Add summarized table --- assets/css/netscope.css | 24 + assets/css/tablesorter.css | 1 + index.html | 7 + presets/inceptionv3.prototxt | 4147 ++++++++++++++++++++++++++++++++++ presets/squeezenet.prototxt | 637 ++++++ quickstart.html | 11 +- src/app.coffee | 6 +- src/caffe/caffe.coffee | 88 +- src/netscope.coffee | 2 +- src/network.coffee | 6 +- src/renderer.coffee | 71 +- 11 files changed, 4980 insertions(+), 20 deletions(-) create mode 100644 assets/css/tablesorter.css create mode 100755 presets/inceptionv3.prototxt create mode 100755 presets/squeezenet.prototxt diff --git a/assets/css/netscope.css b/assets/css/netscope.css index a06c3f9..ced0c8c 100644 --- a/assets/css/netscope.css +++ b/assets/css/netscope.css @@ -51,6 +51,30 @@ h1, h2, h3 { text-align: center; } +#table-container { + display: none; + text-align: center; + padding-top:20px; +} + +#table-header { + padding-top: 20px; + border-top: 1px solid #554037; + min-width: 400px; + text-align: center; +} + +#table-content { + text-align: center; +} + +#table-content table { + display: inline-block; + text-align: left; + width:auto; + +} + #net-group { display: inline-block; } diff --git a/assets/css/tablesorter.css b/assets/css/tablesorter.css new file mode 100644 index 0000000..eb7daf9 --- /dev/null +++ b/assets/css/tablesorter.css @@ -0,0 +1 @@ +.tablesorter-default{width:100%;color:#333;border-spacing:0;margin:10px 0 15px;text-align:left}.tablesorter-default th,.tablesorter-default thead td{font-weight:700;color:#000;border-collapse:collapse;border-bottom:#ccc 2px solid;padding:0}.tablesorter-default tfoot td,.tablesorter-default tfoot th{border:0}.tablesorter-default .header,.tablesorter-default .tablesorter-header{background-image:url(data:image/gif;base64,R0lGODlhFQAJAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==);background-position:center right;background-repeat:no-repeat;cursor:pointer;white-space:normal;padding:4px 20px 4px 4px}.tablesorter-default thead .headerSortUp,.tablesorter-default thead .tablesorter-headerAsc,.tablesorter-default thead .tablesorter-headerSortUp{background-image:url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7);border-bottom:#000 2px solid}.tablesorter-default thead .headerSortDown,.tablesorter-default thead .tablesorter-headerDesc,.tablesorter-default thead .tablesorter-headerSortDown{background-image:url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7);border-bottom:#000 2px solid}.tablesorter-default thead .sorter-false{background-image:none;cursor:default;padding:4px}.tablesorter-default tfoot .tablesorter-headerAsc,.tablesorter-default tfoot .tablesorter-headerDesc,.tablesorter-default tfoot .tablesorter-headerSortDown,.tablesorter-default tfoot .tablesorter-headerSortUp{border-top:#000 2px solid}.tablesorter-default td{background-color:#fff;border-bottom:#ccc 1px solid;padding:4px;vertical-align:top}.tablesorter-default tbody>tr.even:hover>td,.tablesorter-default tbody>tr.hover>td,.tablesorter-default tbody>tr.odd:hover>td,.tablesorter-default tbody>tr:hover>td{background-color:#fff;color:#000}.tablesorter-default .tablesorter-processing{background-position:center center!important;background-repeat:no-repeat!important;background-image:url(data:image/gif;base64,R0lGODlhFAAUAKEAAO7u7lpaWgAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQBCgACACwAAAAAFAAUAAACQZRvoIDtu1wLQUAlqKTVxqwhXIiBnDg6Y4eyx4lKW5XK7wrLeK3vbq8J2W4T4e1nMhpWrZCTt3xKZ8kgsggdJmUFACH5BAEKAAIALAcAAAALAAcAAAIUVB6ii7jajgCAuUmtovxtXnmdUAAAIfkEAQoAAgAsDQACAAcACwAAAhRUIpmHy/3gUVQAQO9NetuugCFWAAAh+QQBCgACACwNAAcABwALAAACE5QVcZjKbVo6ck2AF95m5/6BSwEAIfkEAQoAAgAsBwANAAsABwAAAhOUH3kr6QaAcSrGWe1VQl+mMUIBACH5BAEKAAIALAIADQALAAcAAAIUlICmh7ncTAgqijkruDiv7n2YUAAAIfkEAQoAAgAsAAAHAAcACwAAAhQUIGmHyedehIoqFXLKfPOAaZdWAAAh+QQFCgACACwAAAIABwALAAACFJQFcJiXb15zLYRl7cla8OtlGGgUADs=)!important}.tablesorter-default tr.odd>td{background-color:#dfdfdf}.tablesorter-default tr.even>td{background-color:#efefef}.tablesorter-default tr.odd td.primary{background-color:#bfbfbf}.tablesorter-default td.primary,.tablesorter-default tr.even td.primary,.tablesorter-default tr.odd td.secondary{background-color:#d9d9d9}.tablesorter-default td.secondary,.tablesorter-default tr.even td.secondary,.tablesorter-default tr.odd td.tertiary{background-color:#e6e6e6}.tablesorter-default td.tertiary,.tablesorter-default tr.even td.tertiary{background-color:#f2f2f2}caption{background-color:#fff}.tablesorter-default .tablesorter-filter-row{background-color:#eee}.tablesorter-default .tablesorter-filter-row td{background-color:#eee;border-bottom:#ccc 1px solid;line-height:normal;text-align:center;-webkit-transition:line-height .1s ease;-moz-transition:line-height .1s ease;-o-transition:line-height .1s ease;transition:line-height .1s ease}.tablesorter-default .tablesorter-filter-row .disabled{opacity:.5;filter:alpha(opacity=50);cursor:not-allowed}.tablesorter-default .tablesorter-filter-row.hideme td{padding:2px;margin:0;line-height:0;cursor:pointer}.tablesorter-default .tablesorter-filter-row.hideme *{height:1px;min-height:0;border:0;padding:0;margin:0;opacity:0;filter:alpha(opacity=0)}.tablesorter-default input.tablesorter-filter,.tablesorter-default select.tablesorter-filter{width:95%;height:auto;margin:4px auto;padding:4px;background-color:#fff;border:1px solid #bbb;color:#333;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:height .1s ease;-moz-transition:height .1s ease;-o-transition:height .1s ease;transition:height .1s ease}.tablesorter .filtered{display:none}.tablesorter .tablesorter-errorRow td{text-align:center;cursor:pointer;background-color:#e6bf99} \ No newline at end of file diff --git a/index.html b/index.html index 5f4fa49..894ab25 100644 --- a/index.html +++ b/index.html @@ -7,6 +7,7 @@ + @@ -31,6 +32,12 @@

+
+

Network Analysis

+
+
+ summarize table +
diff --git a/presets/inceptionv3.prototxt b/presets/inceptionv3.prototxt new file mode 100755 index 0000000..2897614 --- /dev/null +++ b/presets/inceptionv3.prototxt @@ -0,0 +1,4147 @@ +name: "InceptionV3" +layer { + name: "data" + type: "Data" + top: "data" + top: "label" + include { + phase: TRAIN + } + transform_param { + mirror: true + crop_size: 299 + mean_value: 104 + mean_value: 117 + mean_value: 123 + } + data_param { + source: "/mnt/disk/ILSVRC2012/300px_ilsvrc12_train_lmdb" + batch_size: 20 + backend: LMDB + } +} +layer { + name: "data" + type: "Data" + top: "data" + top: "label" + include { + phase: TEST + } + transform_param { + mirror: false + crop_size: 299 + mean_value: 104 + mean_value: 117 + mean_value: 123 + } + data_param { + source: "/mnt/disk/ILSVRC2012/300px_ilsvrc12_val_lmdb" + batch_size: 5 + backend: LMDB + } +}layer { + name: "conv_conv2d" + type: "Convolution" + bottom: "data" + top: "conv_conv2d" + convolution_param { + bias_term: false + num_output: 32 + pad: 0 + kernel_size: 3 + stride: 2 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "conv_batchnorm" + type: "BatchNorm" + bottom: "conv_conv2d" + top: "conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "conv_relu" + type: "ReLU" + bottom: "conv_conv2d_bn" + top: "conv_conv2d_relu" +}layer { + name: "conv_1_1_conv2d" + type: "Convolution" + bottom: "conv_conv2d_relu" + top: "conv_1_1_conv2d" + convolution_param { + bias_term: false + num_output: 32 + pad: 0 + kernel_size: 3 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "conv_1_1_batchnorm" + type: "BatchNorm" + bottom: "conv_1_1_conv2d" + top: "conv_1_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "conv_1_1_relu" + type: "ReLU" + bottom: "conv_1_1_conv2d_bn" + top: "conv_1_1_conv2d_relu" +}layer { + name: "conv_2_2_conv2d" + type: "Convolution" + bottom: "conv_1_1_conv2d_relu" + top: "conv_2_2_conv2d" + convolution_param { + bias_term: false + num_output: 64 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "conv_2_2_batchnorm" + type: "BatchNorm" + bottom: "conv_2_2_conv2d" + top: "conv_2_2_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "conv_2_2_relu" + type: "ReLU" + bottom: "conv_2_2_conv2d_bn" + top: "conv_2_2_conv2d_relu" +}layer { + name: "pool" + type: "Pooling" + bottom: "conv_2_2_conv2d_relu" + top: "pool" + pooling_param { + pool: MAX + pad: 0 + kernel_size: 3 + stride: 2 + } +}layer { + name: "conv_3_3_conv2d" + type: "Convolution" + bottom: "pool" + top: "conv_3_3_conv2d" + convolution_param { + bias_term: false + num_output: 80 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "conv_3_3_batchnorm" + type: "BatchNorm" + bottom: "conv_3_3_conv2d" + top: "conv_3_3_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "conv_3_3_relu" + type: "ReLU" + bottom: "conv_3_3_conv2d_bn" + top: "conv_3_3_conv2d_relu" +}layer { + name: "conv_4_4_conv2d" + type: "Convolution" + bottom: "conv_3_3_conv2d_relu" + top: "conv_4_4_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad: 0 + kernel_size: 3 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "conv_4_4_batchnorm" + type: "BatchNorm" + bottom: "conv_4_4_conv2d" + top: "conv_4_4_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "conv_4_4_relu" + type: "ReLU" + bottom: "conv_4_4_conv2d_bn" + top: "conv_4_4_conv2d_relu" +}layer { + name: "pool1" + type: "Pooling" + bottom: "conv_4_4_conv2d_relu" + top: "pool1" + pooling_param { + pool: MAX + pad: 0 + kernel_size: 3 + stride: 2 + } +}layer { + name: "mixed_conv_conv2d" + type: "Convolution" + bottom: "pool1" + top: "mixed_conv_conv2d" + convolution_param { + bias_term: false + num_output: 64 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_conv_conv2d" + top: "mixed_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_conv_relu" + type: "ReLU" + bottom: "mixed_conv_conv2d_bn" + top: "mixed_conv_conv2d_relu" +}layer { + name: "mixed_tower_conv_conv2d" + type: "Convolution" + bottom: "pool1" + top: "mixed_tower_conv_conv2d" + convolution_param { + bias_term: false + num_output: 48 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_tower_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_tower_conv_conv2d" + top: "mixed_tower_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_tower_conv_relu" + type: "ReLU" + bottom: "mixed_tower_conv_conv2d_bn" + top: "mixed_tower_conv_conv2d_relu" +}layer { + name: "mixed_tower_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_tower_conv_conv2d_relu" + top: "mixed_tower_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 64 + pad: 2 + kernel_size: 5 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_tower_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_tower_conv_1_conv2d" + top: "mixed_tower_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_tower_conv_1_relu" + type: "ReLU" + bottom: "mixed_tower_conv_1_conv2d_bn" + top: "mixed_tower_conv_1_conv2d_relu" +}layer { + name: "mixed_tower_1_conv_conv2d" + type: "Convolution" + bottom: "pool1" + top: "mixed_tower_1_conv_conv2d" + convolution_param { + bias_term: false + num_output: 64 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_tower_1_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_tower_1_conv_conv2d" + top: "mixed_tower_1_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_tower_1_conv_relu" + type: "ReLU" + bottom: "mixed_tower_1_conv_conv2d_bn" + top: "mixed_tower_1_conv_conv2d_relu" +}layer { + name: "mixed_tower_1_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_tower_1_conv_conv2d_relu" + top: "mixed_tower_1_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 96 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_tower_1_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_tower_1_conv_1_conv2d" + top: "mixed_tower_1_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_tower_1_conv_1_relu" + type: "ReLU" + bottom: "mixed_tower_1_conv_1_conv2d_bn" + top: "mixed_tower_1_conv_1_conv2d_relu" +}layer { + name: "mixed_tower_1_conv_2_conv2d" + type: "Convolution" + bottom: "mixed_tower_1_conv_1_conv2d_relu" + top: "mixed_tower_1_conv_2_conv2d" + convolution_param { + bias_term: false + num_output: 96 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_tower_1_conv_2_batchnorm" + type: "BatchNorm" + bottom: "mixed_tower_1_conv_2_conv2d" + top: "mixed_tower_1_conv_2_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_tower_1_conv_2_relu" + type: "ReLU" + bottom: "mixed_tower_1_conv_2_conv2d_bn" + top: "mixed_tower_1_conv_2_conv2d_relu" +}layer { + name: "AVE_pool_mixed_pool" + type: "Pooling" + bottom: "pool1" + top: "AVE_pool_mixed_pool" + pooling_param { + pool: AVE + pad: 1 + kernel_size: 3 + stride: 1 + } +}layer { + name: "mixed_tower_2_conv_conv2d" + type: "Convolution" + bottom: "AVE_pool_mixed_pool" + top: "mixed_tower_2_conv_conv2d" + convolution_param { + bias_term: false + num_output: 32 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_tower_2_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_tower_2_conv_conv2d" + top: "mixed_tower_2_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_tower_2_conv_relu" + type: "ReLU" + bottom: "mixed_tower_2_conv_conv2d_bn" + top: "mixed_tower_2_conv_conv2d_relu" +} +layer { + name: "ch_concat_mixed_chconcat" + bottom: "mixed_conv_conv2d_relu" +bottom: "mixed_tower_conv_1_conv2d_relu" +bottom: "mixed_tower_1_conv_2_conv2d_relu" +bottom: "mixed_tower_2_conv_conv2d_relu" + + top: "ch_concat_mixed_chconcat" + type: "Concat" + concat_param { + axis: 1 + } +} +layer { + name: "mixed_1_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_chconcat" + top: "mixed_1_conv_conv2d" + convolution_param { + bias_term: false + num_output: 64 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_1_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_1_conv_conv2d" + top: "mixed_1_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_1_conv_relu" + type: "ReLU" + bottom: "mixed_1_conv_conv2d_bn" + top: "mixed_1_conv_conv2d_relu" +}layer { + name: "mixed_1_tower_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_chconcat" + top: "mixed_1_tower_conv_conv2d" + convolution_param { + bias_term: false + num_output: 48 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_1_tower_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_1_tower_conv_conv2d" + top: "mixed_1_tower_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_1_tower_conv_relu" + type: "ReLU" + bottom: "mixed_1_tower_conv_conv2d_bn" + top: "mixed_1_tower_conv_conv2d_relu" +}layer { + name: "mixed_1_tower_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_1_tower_conv_conv2d_relu" + top: "mixed_1_tower_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 64 + pad: 2 + kernel_size: 5 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_1_tower_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_1_tower_conv_1_conv2d" + top: "mixed_1_tower_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_1_tower_conv_1_relu" + type: "ReLU" + bottom: "mixed_1_tower_conv_1_conv2d_bn" + top: "mixed_1_tower_conv_1_conv2d_relu" +}layer { + name: "mixed_1_tower_1_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_chconcat" + top: "mixed_1_tower_1_conv_conv2d" + convolution_param { + bias_term: false + num_output: 64 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_1_tower_1_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_1_tower_1_conv_conv2d" + top: "mixed_1_tower_1_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_1_tower_1_conv_relu" + type: "ReLU" + bottom: "mixed_1_tower_1_conv_conv2d_bn" + top: "mixed_1_tower_1_conv_conv2d_relu" +}layer { + name: "mixed_1_tower_1_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_1_tower_1_conv_conv2d_relu" + top: "mixed_1_tower_1_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 96 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_1_tower_1_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_1_tower_1_conv_1_conv2d" + top: "mixed_1_tower_1_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_1_tower_1_conv_1_relu" + type: "ReLU" + bottom: "mixed_1_tower_1_conv_1_conv2d_bn" + top: "mixed_1_tower_1_conv_1_conv2d_relu" +}layer { + name: "mixed_1_tower_1_conv_2_conv2d" + type: "Convolution" + bottom: "mixed_1_tower_1_conv_1_conv2d_relu" + top: "mixed_1_tower_1_conv_2_conv2d" + convolution_param { + bias_term: false + num_output: 96 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_1_tower_1_conv_2_batchnorm" + type: "BatchNorm" + bottom: "mixed_1_tower_1_conv_2_conv2d" + top: "mixed_1_tower_1_conv_2_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_1_tower_1_conv_2_relu" + type: "ReLU" + bottom: "mixed_1_tower_1_conv_2_conv2d_bn" + top: "mixed_1_tower_1_conv_2_conv2d_relu" +}layer { + name: "AVE_pool_mixed_1_pool" + type: "Pooling" + bottom: "ch_concat_mixed_chconcat" + top: "AVE_pool_mixed_1_pool" + pooling_param { + pool: AVE + pad: 1 + kernel_size: 3 + stride: 1 + } +}layer { + name: "mixed_1_tower_2_conv_conv2d" + type: "Convolution" + bottom: "AVE_pool_mixed_1_pool" + top: "mixed_1_tower_2_conv_conv2d" + convolution_param { + bias_term: false + num_output: 64 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_1_tower_2_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_1_tower_2_conv_conv2d" + top: "mixed_1_tower_2_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_1_tower_2_conv_relu" + type: "ReLU" + bottom: "mixed_1_tower_2_conv_conv2d_bn" + top: "mixed_1_tower_2_conv_conv2d_relu" +} +layer { + name: "ch_concat_mixed_1_chconcat" + bottom: "mixed_1_conv_conv2d_relu" +bottom: "mixed_1_tower_conv_1_conv2d_relu" +bottom: "mixed_1_tower_1_conv_2_conv2d_relu" +bottom: "mixed_1_tower_2_conv_conv2d_relu" + + top: "ch_concat_mixed_1_chconcat" + type: "Concat" + concat_param { + axis: 1 + } +} +layer { + name: "mixed_2_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_1_chconcat" + top: "mixed_2_conv_conv2d" + convolution_param { + bias_term: false + num_output: 64 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_2_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_2_conv_conv2d" + top: "mixed_2_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_2_conv_relu" + type: "ReLU" + bottom: "mixed_2_conv_conv2d_bn" + top: "mixed_2_conv_conv2d_relu" +}layer { + name: "mixed_2_tower_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_1_chconcat" + top: "mixed_2_tower_conv_conv2d" + convolution_param { + bias_term: false + num_output: 48 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_2_tower_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_2_tower_conv_conv2d" + top: "mixed_2_tower_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_2_tower_conv_relu" + type: "ReLU" + bottom: "mixed_2_tower_conv_conv2d_bn" + top: "mixed_2_tower_conv_conv2d_relu" +}layer { + name: "mixed_2_tower_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_2_tower_conv_conv2d_relu" + top: "mixed_2_tower_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 64 + pad: 2 + kernel_size: 5 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_2_tower_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_2_tower_conv_1_conv2d" + top: "mixed_2_tower_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_2_tower_conv_1_relu" + type: "ReLU" + bottom: "mixed_2_tower_conv_1_conv2d_bn" + top: "mixed_2_tower_conv_1_conv2d_relu" +}layer { + name: "mixed_2_tower_1_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_1_chconcat" + top: "mixed_2_tower_1_conv_conv2d" + convolution_param { + bias_term: false + num_output: 64 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_2_tower_1_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_2_tower_1_conv_conv2d" + top: "mixed_2_tower_1_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_2_tower_1_conv_relu" + type: "ReLU" + bottom: "mixed_2_tower_1_conv_conv2d_bn" + top: "mixed_2_tower_1_conv_conv2d_relu" +}layer { + name: "mixed_2_tower_1_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_2_tower_1_conv_conv2d_relu" + top: "mixed_2_tower_1_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 96 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_2_tower_1_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_2_tower_1_conv_1_conv2d" + top: "mixed_2_tower_1_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_2_tower_1_conv_1_relu" + type: "ReLU" + bottom: "mixed_2_tower_1_conv_1_conv2d_bn" + top: "mixed_2_tower_1_conv_1_conv2d_relu" +}layer { + name: "mixed_2_tower_1_conv_2_conv2d" + type: "Convolution" + bottom: "mixed_2_tower_1_conv_1_conv2d_relu" + top: "mixed_2_tower_1_conv_2_conv2d" + convolution_param { + bias_term: false + num_output: 96 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_2_tower_1_conv_2_batchnorm" + type: "BatchNorm" + bottom: "mixed_2_tower_1_conv_2_conv2d" + top: "mixed_2_tower_1_conv_2_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_2_tower_1_conv_2_relu" + type: "ReLU" + bottom: "mixed_2_tower_1_conv_2_conv2d_bn" + top: "mixed_2_tower_1_conv_2_conv2d_relu" +}layer { + name: "AVE_pool_mixed_2_pool" + type: "Pooling" + bottom: "ch_concat_mixed_1_chconcat" + top: "AVE_pool_mixed_2_pool" + pooling_param { + pool: AVE + pad: 1 + kernel_size: 3 + stride: 1 + } +}layer { + name: "mixed_2_tower_2_conv_conv2d" + type: "Convolution" + bottom: "AVE_pool_mixed_2_pool" + top: "mixed_2_tower_2_conv_conv2d" + convolution_param { + bias_term: false + num_output: 64 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_2_tower_2_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_2_tower_2_conv_conv2d" + top: "mixed_2_tower_2_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_2_tower_2_conv_relu" + type: "ReLU" + bottom: "mixed_2_tower_2_conv_conv2d_bn" + top: "mixed_2_tower_2_conv_conv2d_relu" +} +layer { + name: "ch_concat_mixed_2_chconcat" + bottom: "mixed_2_conv_conv2d_relu" +bottom: "mixed_2_tower_conv_1_conv2d_relu" +bottom: "mixed_2_tower_1_conv_2_conv2d_relu" +bottom: "mixed_2_tower_2_conv_conv2d_relu" + + top: "ch_concat_mixed_2_chconcat" + type: "Concat" + concat_param { + axis: 1 + } +} +layer { + name: "mixed_3_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_2_chconcat" + top: "mixed_3_conv_conv2d" + convolution_param { + bias_term: false + num_output: 384 + pad: 0 + kernel_size: 3 + stride: 2 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_3_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_3_conv_conv2d" + top: "mixed_3_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_3_conv_relu" + type: "ReLU" + bottom: "mixed_3_conv_conv2d_bn" + top: "mixed_3_conv_conv2d_relu" +}layer { + name: "mixed_3_tower_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_2_chconcat" + top: "mixed_3_tower_conv_conv2d" + convolution_param { + bias_term: false + num_output: 64 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_3_tower_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_3_tower_conv_conv2d" + top: "mixed_3_tower_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_3_tower_conv_relu" + type: "ReLU" + bottom: "mixed_3_tower_conv_conv2d_bn" + top: "mixed_3_tower_conv_conv2d_relu" +}layer { + name: "mixed_3_tower_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_3_tower_conv_conv2d_relu" + top: "mixed_3_tower_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 96 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_3_tower_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_3_tower_conv_1_conv2d" + top: "mixed_3_tower_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_3_tower_conv_1_relu" + type: "ReLU" + bottom: "mixed_3_tower_conv_1_conv2d_bn" + top: "mixed_3_tower_conv_1_conv2d_relu" +}layer { + name: "mixed_3_tower_conv_2_conv2d" + type: "Convolution" + bottom: "mixed_3_tower_conv_1_conv2d_relu" + top: "mixed_3_tower_conv_2_conv2d" + convolution_param { + bias_term: false + num_output: 96 + pad: 0 + kernel_size: 3 + stride: 2 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_3_tower_conv_2_batchnorm" + type: "BatchNorm" + bottom: "mixed_3_tower_conv_2_conv2d" + top: "mixed_3_tower_conv_2_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_3_tower_conv_2_relu" + type: "ReLU" + bottom: "mixed_3_tower_conv_2_conv2d_bn" + top: "mixed_3_tower_conv_2_conv2d_relu" +}layer { + name: "max_pool_mixed_3_pool" + type: "Pooling" + bottom: "ch_concat_mixed_2_chconcat" + top: "max_pool_mixed_3_pool" + pooling_param { + pool: MAX + pad: 0 + kernel_size: 3 + stride: 2 + } +} +layer { + name: "ch_concat_mixed_3_chconcat" + bottom: "max_pool_mixed_3_pool" +bottom: "mixed_3_conv_conv2d_relu" +bottom: "mixed_3_tower_conv_2_conv2d_relu" + + top: "ch_concat_mixed_3_chconcat" + type: "Concat" + concat_param { + axis: 1 + } +} +layer { + name: "mixed_4_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_3_chconcat" + top: "mixed_4_conv_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_4_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_4_conv_conv2d" + top: "mixed_4_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_4_conv_relu" + type: "ReLU" + bottom: "mixed_4_conv_conv2d_bn" + top: "mixed_4_conv_conv2d_relu" +}layer { + name: "mixed_4_tower_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_3_chconcat" + top: "mixed_4_tower_conv_conv2d" + convolution_param { + bias_term: false + num_output: 128 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_4_tower_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_4_tower_conv_conv2d" + top: "mixed_4_tower_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_4_tower_conv_relu" + type: "ReLU" + bottom: "mixed_4_tower_conv_conv2d_bn" + top: "mixed_4_tower_conv_conv2d_relu" +}layer { + name: "mixed_4_tower_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_4_tower_conv_conv2d_relu" + top: "mixed_4_tower_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 128 + pad_h: 0 +pad_w: 3 + kernel_h: 1 +kernel_w: 7 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_4_tower_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_4_tower_conv_1_conv2d" + top: "mixed_4_tower_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_4_tower_conv_1_relu" + type: "ReLU" + bottom: "mixed_4_tower_conv_1_conv2d_bn" + top: "mixed_4_tower_conv_1_conv2d_relu" +}layer { + name: "mixed_4_tower_conv_2_conv2d" + type: "Convolution" + bottom: "mixed_4_tower_conv_1_conv2d_relu" + top: "mixed_4_tower_conv_2_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad_h: 3 +pad_w: 0 + kernel_h: 7 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_4_tower_conv_2_batchnorm" + type: "BatchNorm" + bottom: "mixed_4_tower_conv_2_conv2d" + top: "mixed_4_tower_conv_2_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_4_tower_conv_2_relu" + type: "ReLU" + bottom: "mixed_4_tower_conv_2_conv2d_bn" + top: "mixed_4_tower_conv_2_conv2d_relu" +}layer { + name: "mixed_4_tower_1_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_3_chconcat" + top: "mixed_4_tower_1_conv_conv2d" + convolution_param { + bias_term: false + num_output: 128 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_4_tower_1_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_4_tower_1_conv_conv2d" + top: "mixed_4_tower_1_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_4_tower_1_conv_relu" + type: "ReLU" + bottom: "mixed_4_tower_1_conv_conv2d_bn" + top: "mixed_4_tower_1_conv_conv2d_relu" +}layer { + name: "mixed_4_tower_1_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_4_tower_1_conv_conv2d_relu" + top: "mixed_4_tower_1_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 128 + pad_h: 3 +pad_w: 0 + kernel_h: 7 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_4_tower_1_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_4_tower_1_conv_1_conv2d" + top: "mixed_4_tower_1_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_4_tower_1_conv_1_relu" + type: "ReLU" + bottom: "mixed_4_tower_1_conv_1_conv2d_bn" + top: "mixed_4_tower_1_conv_1_conv2d_relu" +}layer { + name: "mixed_4_tower_1_conv_2_conv2d" + type: "Convolution" + bottom: "mixed_4_tower_1_conv_1_conv2d_relu" + top: "mixed_4_tower_1_conv_2_conv2d" + convolution_param { + bias_term: false + num_output: 128 + pad_h: 0 +pad_w: 3 + kernel_h: 1 +kernel_w: 7 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_4_tower_1_conv_2_batchnorm" + type: "BatchNorm" + bottom: "mixed_4_tower_1_conv_2_conv2d" + top: "mixed_4_tower_1_conv_2_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_4_tower_1_conv_2_relu" + type: "ReLU" + bottom: "mixed_4_tower_1_conv_2_conv2d_bn" + top: "mixed_4_tower_1_conv_2_conv2d_relu" +}layer { + name: "mixed_4_tower_1_conv_3_conv2d" + type: "Convolution" + bottom: "mixed_4_tower_1_conv_2_conv2d_relu" + top: "mixed_4_tower_1_conv_3_conv2d" + convolution_param { + bias_term: false + num_output: 128 + pad_h: 3 +pad_w: 0 + kernel_h: 7 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_4_tower_1_conv_3_batchnorm" + type: "BatchNorm" + bottom: "mixed_4_tower_1_conv_3_conv2d" + top: "mixed_4_tower_1_conv_3_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_4_tower_1_conv_3_relu" + type: "ReLU" + bottom: "mixed_4_tower_1_conv_3_conv2d_bn" + top: "mixed_4_tower_1_conv_3_conv2d_relu" +}layer { + name: "mixed_4_tower_1_conv_4_conv2d" + type: "Convolution" + bottom: "mixed_4_tower_1_conv_3_conv2d_relu" + top: "mixed_4_tower_1_conv_4_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad_h: 0 +pad_w: 3 + kernel_h: 1 +kernel_w: 7 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_4_tower_1_conv_4_batchnorm" + type: "BatchNorm" + bottom: "mixed_4_tower_1_conv_4_conv2d" + top: "mixed_4_tower_1_conv_4_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_4_tower_1_conv_4_relu" + type: "ReLU" + bottom: "mixed_4_tower_1_conv_4_conv2d_bn" + top: "mixed_4_tower_1_conv_4_conv2d_relu" +}layer { + name: "AVE_pool_mixed_4_pool" + type: "Pooling" + bottom: "ch_concat_mixed_3_chconcat" + top: "AVE_pool_mixed_4_pool" + pooling_param { + pool: AVE + pad: 1 + kernel_size: 3 + stride: 1 + } +}layer { + name: "mixed_4_tower_2_conv_conv2d" + type: "Convolution" + bottom: "AVE_pool_mixed_4_pool" + top: "mixed_4_tower_2_conv_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_4_tower_2_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_4_tower_2_conv_conv2d" + top: "mixed_4_tower_2_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_4_tower_2_conv_relu" + type: "ReLU" + bottom: "mixed_4_tower_2_conv_conv2d_bn" + top: "mixed_4_tower_2_conv_conv2d_relu" +} +layer { + name: "ch_concat_mixed_4_chconcat" + bottom: "mixed_4_conv_conv2d_relu" +bottom: "mixed_4_tower_conv_2_conv2d_relu" +bottom: "mixed_4_tower_1_conv_4_conv2d_relu" +bottom: "mixed_4_tower_2_conv_conv2d_relu" + + top: "ch_concat_mixed_4_chconcat" + type: "Concat" + concat_param { + axis: 1 + } +} +layer { + name: "mixed_5_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_4_chconcat" + top: "mixed_5_conv_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_5_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_5_conv_conv2d" + top: "mixed_5_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_5_conv_relu" + type: "ReLU" + bottom: "mixed_5_conv_conv2d_bn" + top: "mixed_5_conv_conv2d_relu" +}layer { + name: "mixed_5_tower_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_4_chconcat" + top: "mixed_5_tower_conv_conv2d" + convolution_param { + bias_term: false + num_output: 160 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_5_tower_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_5_tower_conv_conv2d" + top: "mixed_5_tower_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_5_tower_conv_relu" + type: "ReLU" + bottom: "mixed_5_tower_conv_conv2d_bn" + top: "mixed_5_tower_conv_conv2d_relu" +}layer { + name: "mixed_5_tower_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_5_tower_conv_conv2d_relu" + top: "mixed_5_tower_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 160 + pad_h: 0 +pad_w: 3 + kernel_h: 1 +kernel_w: 7 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_5_tower_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_5_tower_conv_1_conv2d" + top: "mixed_5_tower_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_5_tower_conv_1_relu" + type: "ReLU" + bottom: "mixed_5_tower_conv_1_conv2d_bn" + top: "mixed_5_tower_conv_1_conv2d_relu" +}layer { + name: "mixed_5_tower_conv_2_conv2d" + type: "Convolution" + bottom: "mixed_5_tower_conv_1_conv2d_relu" + top: "mixed_5_tower_conv_2_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad_h: 3 +pad_w: 0 + kernel_h: 7 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_5_tower_conv_2_batchnorm" + type: "BatchNorm" + bottom: "mixed_5_tower_conv_2_conv2d" + top: "mixed_5_tower_conv_2_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_5_tower_conv_2_relu" + type: "ReLU" + bottom: "mixed_5_tower_conv_2_conv2d_bn" + top: "mixed_5_tower_conv_2_conv2d_relu" +}layer { + name: "mixed_5_tower_1_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_4_chconcat" + top: "mixed_5_tower_1_conv_conv2d" + convolution_param { + bias_term: false + num_output: 160 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_5_tower_1_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_5_tower_1_conv_conv2d" + top: "mixed_5_tower_1_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_5_tower_1_conv_relu" + type: "ReLU" + bottom: "mixed_5_tower_1_conv_conv2d_bn" + top: "mixed_5_tower_1_conv_conv2d_relu" +}layer { + name: "mixed_5_tower_1_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_5_tower_1_conv_conv2d_relu" + top: "mixed_5_tower_1_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 160 + pad_h: 3 +pad_w: 0 + kernel_h: 7 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_5_tower_1_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_5_tower_1_conv_1_conv2d" + top: "mixed_5_tower_1_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_5_tower_1_conv_1_relu" + type: "ReLU" + bottom: "mixed_5_tower_1_conv_1_conv2d_bn" + top: "mixed_5_tower_1_conv_1_conv2d_relu" +}layer { + name: "mixed_5_tower_1_conv_2_conv2d" + type: "Convolution" + bottom: "mixed_5_tower_1_conv_1_conv2d_relu" + top: "mixed_5_tower_1_conv_2_conv2d" + convolution_param { + bias_term: false + num_output: 160 + pad_h: 0 +pad_w: 3 + kernel_h: 1 +kernel_w: 7 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_5_tower_1_conv_2_batchnorm" + type: "BatchNorm" + bottom: "mixed_5_tower_1_conv_2_conv2d" + top: "mixed_5_tower_1_conv_2_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_5_tower_1_conv_2_relu" + type: "ReLU" + bottom: "mixed_5_tower_1_conv_2_conv2d_bn" + top: "mixed_5_tower_1_conv_2_conv2d_relu" +}layer { + name: "mixed_5_tower_1_conv_3_conv2d" + type: "Convolution" + bottom: "mixed_5_tower_1_conv_2_conv2d_relu" + top: "mixed_5_tower_1_conv_3_conv2d" + convolution_param { + bias_term: false + num_output: 160 + pad_h: 3 +pad_w: 0 + kernel_h: 7 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_5_tower_1_conv_3_batchnorm" + type: "BatchNorm" + bottom: "mixed_5_tower_1_conv_3_conv2d" + top: "mixed_5_tower_1_conv_3_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_5_tower_1_conv_3_relu" + type: "ReLU" + bottom: "mixed_5_tower_1_conv_3_conv2d_bn" + top: "mixed_5_tower_1_conv_3_conv2d_relu" +}layer { + name: "mixed_5_tower_1_conv_4_conv2d" + type: "Convolution" + bottom: "mixed_5_tower_1_conv_3_conv2d_relu" + top: "mixed_5_tower_1_conv_4_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad_h: 0 +pad_w: 3 + kernel_h: 1 +kernel_w: 7 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_5_tower_1_conv_4_batchnorm" + type: "BatchNorm" + bottom: "mixed_5_tower_1_conv_4_conv2d" + top: "mixed_5_tower_1_conv_4_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_5_tower_1_conv_4_relu" + type: "ReLU" + bottom: "mixed_5_tower_1_conv_4_conv2d_bn" + top: "mixed_5_tower_1_conv_4_conv2d_relu" +}layer { + name: "AVE_pool_mixed_5_pool" + type: "Pooling" + bottom: "ch_concat_mixed_4_chconcat" + top: "AVE_pool_mixed_5_pool" + pooling_param { + pool: AVE + pad: 1 + kernel_size: 3 + stride: 1 + } +}layer { + name: "mixed_5_tower_2_conv_conv2d" + type: "Convolution" + bottom: "AVE_pool_mixed_5_pool" + top: "mixed_5_tower_2_conv_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_5_tower_2_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_5_tower_2_conv_conv2d" + top: "mixed_5_tower_2_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_5_tower_2_conv_relu" + type: "ReLU" + bottom: "mixed_5_tower_2_conv_conv2d_bn" + top: "mixed_5_tower_2_conv_conv2d_relu" +} +layer { + name: "ch_concat_mixed_5_chconcat" + bottom: "mixed_5_conv_conv2d_relu" +bottom: "mixed_5_tower_conv_2_conv2d_relu" +bottom: "mixed_5_tower_1_conv_4_conv2d_relu" +bottom: "mixed_5_tower_2_conv_conv2d_relu" + + top: "ch_concat_mixed_5_chconcat" + type: "Concat" + concat_param { + axis: 1 + } +} +layer { + name: "mixed_6_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_5_chconcat" + top: "mixed_6_conv_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_6_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_6_conv_conv2d" + top: "mixed_6_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_6_conv_relu" + type: "ReLU" + bottom: "mixed_6_conv_conv2d_bn" + top: "mixed_6_conv_conv2d_relu" +}layer { + name: "mixed_6_tower_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_5_chconcat" + top: "mixed_6_tower_conv_conv2d" + convolution_param { + bias_term: false + num_output: 160 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_6_tower_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_6_tower_conv_conv2d" + top: "mixed_6_tower_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_6_tower_conv_relu" + type: "ReLU" + bottom: "mixed_6_tower_conv_conv2d_bn" + top: "mixed_6_tower_conv_conv2d_relu" +}layer { + name: "mixed_6_tower_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_6_tower_conv_conv2d_relu" + top: "mixed_6_tower_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 160 + pad_h: 0 +pad_w: 3 + kernel_h: 1 +kernel_w: 7 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_6_tower_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_6_tower_conv_1_conv2d" + top: "mixed_6_tower_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_6_tower_conv_1_relu" + type: "ReLU" + bottom: "mixed_6_tower_conv_1_conv2d_bn" + top: "mixed_6_tower_conv_1_conv2d_relu" +}layer { + name: "mixed_6_tower_conv_2_conv2d" + type: "Convolution" + bottom: "mixed_6_tower_conv_1_conv2d_relu" + top: "mixed_6_tower_conv_2_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad_h: 3 +pad_w: 0 + kernel_h: 7 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_6_tower_conv_2_batchnorm" + type: "BatchNorm" + bottom: "mixed_6_tower_conv_2_conv2d" + top: "mixed_6_tower_conv_2_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_6_tower_conv_2_relu" + type: "ReLU" + bottom: "mixed_6_tower_conv_2_conv2d_bn" + top: "mixed_6_tower_conv_2_conv2d_relu" +}layer { + name: "mixed_6_tower_1_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_5_chconcat" + top: "mixed_6_tower_1_conv_conv2d" + convolution_param { + bias_term: false + num_output: 160 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_6_tower_1_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_6_tower_1_conv_conv2d" + top: "mixed_6_tower_1_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_6_tower_1_conv_relu" + type: "ReLU" + bottom: "mixed_6_tower_1_conv_conv2d_bn" + top: "mixed_6_tower_1_conv_conv2d_relu" +}layer { + name: "mixed_6_tower_1_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_6_tower_1_conv_conv2d_relu" + top: "mixed_6_tower_1_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 160 + pad_h: 3 +pad_w: 0 + kernel_h: 7 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_6_tower_1_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_6_tower_1_conv_1_conv2d" + top: "mixed_6_tower_1_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_6_tower_1_conv_1_relu" + type: "ReLU" + bottom: "mixed_6_tower_1_conv_1_conv2d_bn" + top: "mixed_6_tower_1_conv_1_conv2d_relu" +}layer { + name: "mixed_6_tower_1_conv_2_conv2d" + type: "Convolution" + bottom: "mixed_6_tower_1_conv_1_conv2d_relu" + top: "mixed_6_tower_1_conv_2_conv2d" + convolution_param { + bias_term: false + num_output: 160 + pad_h: 0 +pad_w: 3 + kernel_h: 1 +kernel_w: 7 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_6_tower_1_conv_2_batchnorm" + type: "BatchNorm" + bottom: "mixed_6_tower_1_conv_2_conv2d" + top: "mixed_6_tower_1_conv_2_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_6_tower_1_conv_2_relu" + type: "ReLU" + bottom: "mixed_6_tower_1_conv_2_conv2d_bn" + top: "mixed_6_tower_1_conv_2_conv2d_relu" +}layer { + name: "mixed_6_tower_1_conv_3_conv2d" + type: "Convolution" + bottom: "mixed_6_tower_1_conv_2_conv2d_relu" + top: "mixed_6_tower_1_conv_3_conv2d" + convolution_param { + bias_term: false + num_output: 160 + pad_h: 3 +pad_w: 0 + kernel_h: 7 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_6_tower_1_conv_3_batchnorm" + type: "BatchNorm" + bottom: "mixed_6_tower_1_conv_3_conv2d" + top: "mixed_6_tower_1_conv_3_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_6_tower_1_conv_3_relu" + type: "ReLU" + bottom: "mixed_6_tower_1_conv_3_conv2d_bn" + top: "mixed_6_tower_1_conv_3_conv2d_relu" +}layer { + name: "mixed_6_tower_1_conv_4_conv2d" + type: "Convolution" + bottom: "mixed_6_tower_1_conv_3_conv2d_relu" + top: "mixed_6_tower_1_conv_4_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad_h: 0 +pad_w: 3 + kernel_h: 1 +kernel_w: 7 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_6_tower_1_conv_4_batchnorm" + type: "BatchNorm" + bottom: "mixed_6_tower_1_conv_4_conv2d" + top: "mixed_6_tower_1_conv_4_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_6_tower_1_conv_4_relu" + type: "ReLU" + bottom: "mixed_6_tower_1_conv_4_conv2d_bn" + top: "mixed_6_tower_1_conv_4_conv2d_relu" +}layer { + name: "AVE_pool_mixed_6_pool" + type: "Pooling" + bottom: "ch_concat_mixed_5_chconcat" + top: "AVE_pool_mixed_6_pool" + pooling_param { + pool: AVE + pad: 1 + kernel_size: 3 + stride: 1 + } +}layer { + name: "mixed_6_tower_2_conv_conv2d" + type: "Convolution" + bottom: "AVE_pool_mixed_6_pool" + top: "mixed_6_tower_2_conv_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_6_tower_2_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_6_tower_2_conv_conv2d" + top: "mixed_6_tower_2_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_6_tower_2_conv_relu" + type: "ReLU" + bottom: "mixed_6_tower_2_conv_conv2d_bn" + top: "mixed_6_tower_2_conv_conv2d_relu" +} +layer { + name: "ch_concat_mixed_6_chconcat" + bottom: "mixed_6_conv_conv2d_relu" +bottom: "mixed_6_tower_conv_2_conv2d_relu" +bottom: "mixed_6_tower_1_conv_4_conv2d_relu" +bottom: "mixed_6_tower_2_conv_conv2d_relu" + + top: "ch_concat_mixed_6_chconcat" + type: "Concat" + concat_param { + axis: 1 + } +} +layer { + name: "mixed_7_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_6_chconcat" + top: "mixed_7_conv_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_7_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_7_conv_conv2d" + top: "mixed_7_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_7_conv_relu" + type: "ReLU" + bottom: "mixed_7_conv_conv2d_bn" + top: "mixed_7_conv_conv2d_relu" +}layer { + name: "mixed_7_tower_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_6_chconcat" + top: "mixed_7_tower_conv_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_7_tower_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_7_tower_conv_conv2d" + top: "mixed_7_tower_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_7_tower_conv_relu" + type: "ReLU" + bottom: "mixed_7_tower_conv_conv2d_bn" + top: "mixed_7_tower_conv_conv2d_relu" +}layer { + name: "mixed_7_tower_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_7_tower_conv_conv2d_relu" + top: "mixed_7_tower_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad_h: 0 +pad_w: 3 + kernel_h: 1 +kernel_w: 7 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_7_tower_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_7_tower_conv_1_conv2d" + top: "mixed_7_tower_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_7_tower_conv_1_relu" + type: "ReLU" + bottom: "mixed_7_tower_conv_1_conv2d_bn" + top: "mixed_7_tower_conv_1_conv2d_relu" +}layer { + name: "mixed_7_tower_conv_2_conv2d" + type: "Convolution" + bottom: "mixed_7_tower_conv_1_conv2d_relu" + top: "mixed_7_tower_conv_2_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad_h: 3 +pad_w: 0 + kernel_h: 7 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_7_tower_conv_2_batchnorm" + type: "BatchNorm" + bottom: "mixed_7_tower_conv_2_conv2d" + top: "mixed_7_tower_conv_2_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_7_tower_conv_2_relu" + type: "ReLU" + bottom: "mixed_7_tower_conv_2_conv2d_bn" + top: "mixed_7_tower_conv_2_conv2d_relu" +}layer { + name: "mixed_7_tower_1_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_6_chconcat" + top: "mixed_7_tower_1_conv_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_7_tower_1_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_7_tower_1_conv_conv2d" + top: "mixed_7_tower_1_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_7_tower_1_conv_relu" + type: "ReLU" + bottom: "mixed_7_tower_1_conv_conv2d_bn" + top: "mixed_7_tower_1_conv_conv2d_relu" +}layer { + name: "mixed_7_tower_1_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_7_tower_1_conv_conv2d_relu" + top: "mixed_7_tower_1_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad_h: 3 +pad_w: 0 + kernel_h: 7 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_7_tower_1_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_7_tower_1_conv_1_conv2d" + top: "mixed_7_tower_1_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_7_tower_1_conv_1_relu" + type: "ReLU" + bottom: "mixed_7_tower_1_conv_1_conv2d_bn" + top: "mixed_7_tower_1_conv_1_conv2d_relu" +}layer { + name: "mixed_7_tower_1_conv_2_conv2d" + type: "Convolution" + bottom: "mixed_7_tower_1_conv_1_conv2d_relu" + top: "mixed_7_tower_1_conv_2_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad_h: 0 +pad_w: 3 + kernel_h: 1 +kernel_w: 7 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_7_tower_1_conv_2_batchnorm" + type: "BatchNorm" + bottom: "mixed_7_tower_1_conv_2_conv2d" + top: "mixed_7_tower_1_conv_2_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_7_tower_1_conv_2_relu" + type: "ReLU" + bottom: "mixed_7_tower_1_conv_2_conv2d_bn" + top: "mixed_7_tower_1_conv_2_conv2d_relu" +}layer { + name: "mixed_7_tower_1_conv_3_conv2d" + type: "Convolution" + bottom: "mixed_7_tower_1_conv_2_conv2d_relu" + top: "mixed_7_tower_1_conv_3_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad_h: 3 +pad_w: 0 + kernel_h: 7 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_7_tower_1_conv_3_batchnorm" + type: "BatchNorm" + bottom: "mixed_7_tower_1_conv_3_conv2d" + top: "mixed_7_tower_1_conv_3_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_7_tower_1_conv_3_relu" + type: "ReLU" + bottom: "mixed_7_tower_1_conv_3_conv2d_bn" + top: "mixed_7_tower_1_conv_3_conv2d_relu" +}layer { + name: "mixed_7_tower_1_conv_4_conv2d" + type: "Convolution" + bottom: "mixed_7_tower_1_conv_3_conv2d_relu" + top: "mixed_7_tower_1_conv_4_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad_h: 0 +pad_w: 3 + kernel_h: 1 +kernel_w: 7 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_7_tower_1_conv_4_batchnorm" + type: "BatchNorm" + bottom: "mixed_7_tower_1_conv_4_conv2d" + top: "mixed_7_tower_1_conv_4_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_7_tower_1_conv_4_relu" + type: "ReLU" + bottom: "mixed_7_tower_1_conv_4_conv2d_bn" + top: "mixed_7_tower_1_conv_4_conv2d_relu" +}layer { + name: "AVE_pool_mixed_7_pool" + type: "Pooling" + bottom: "ch_concat_mixed_6_chconcat" + top: "AVE_pool_mixed_7_pool" + pooling_param { + pool: AVE + pad: 1 + kernel_size: 3 + stride: 1 + } +}layer { + name: "mixed_7_tower_2_conv_conv2d" + type: "Convolution" + bottom: "AVE_pool_mixed_7_pool" + top: "mixed_7_tower_2_conv_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_7_tower_2_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_7_tower_2_conv_conv2d" + top: "mixed_7_tower_2_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_7_tower_2_conv_relu" + type: "ReLU" + bottom: "mixed_7_tower_2_conv_conv2d_bn" + top: "mixed_7_tower_2_conv_conv2d_relu" +} +layer { + name: "ch_concat_mixed_7_chconcat" + bottom: "mixed_7_conv_conv2d_relu" +bottom: "mixed_7_tower_conv_2_conv2d_relu" +bottom: "mixed_7_tower_1_conv_4_conv2d_relu" +bottom: "mixed_7_tower_2_conv_conv2d_relu" + + top: "ch_concat_mixed_7_chconcat" + type: "Concat" + concat_param { + axis: 1 + } +} +layer { + name: "mixed_8_tower_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_7_chconcat" + top: "mixed_8_tower_conv_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_8_tower_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_8_tower_conv_conv2d" + top: "mixed_8_tower_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_8_tower_conv_relu" + type: "ReLU" + bottom: "mixed_8_tower_conv_conv2d_bn" + top: "mixed_8_tower_conv_conv2d_relu" +}layer { + name: "mixed_8_tower_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_8_tower_conv_conv2d_relu" + top: "mixed_8_tower_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 320 + pad: 0 + kernel_size: 3 + stride: 2 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_8_tower_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_8_tower_conv_1_conv2d" + top: "mixed_8_tower_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_8_tower_conv_1_relu" + type: "ReLU" + bottom: "mixed_8_tower_conv_1_conv2d_bn" + top: "mixed_8_tower_conv_1_conv2d_relu" +}layer { + name: "mixed_8_tower_1_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_7_chconcat" + top: "mixed_8_tower_1_conv_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_8_tower_1_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_8_tower_1_conv_conv2d" + top: "mixed_8_tower_1_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_8_tower_1_conv_relu" + type: "ReLU" + bottom: "mixed_8_tower_1_conv_conv2d_bn" + top: "mixed_8_tower_1_conv_conv2d_relu" +}layer { + name: "mixed_8_tower_1_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_8_tower_1_conv_conv2d_relu" + top: "mixed_8_tower_1_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad_h: 0 +pad_w: 3 + kernel_h: 1 +kernel_w: 7 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_8_tower_1_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_8_tower_1_conv_1_conv2d" + top: "mixed_8_tower_1_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_8_tower_1_conv_1_relu" + type: "ReLU" + bottom: "mixed_8_tower_1_conv_1_conv2d_bn" + top: "mixed_8_tower_1_conv_1_conv2d_relu" +}layer { + name: "mixed_8_tower_1_conv_2_conv2d" + type: "Convolution" + bottom: "mixed_8_tower_1_conv_1_conv2d_relu" + top: "mixed_8_tower_1_conv_2_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad_h: 3 +pad_w: 0 + kernel_h: 7 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_8_tower_1_conv_2_batchnorm" + type: "BatchNorm" + bottom: "mixed_8_tower_1_conv_2_conv2d" + top: "mixed_8_tower_1_conv_2_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_8_tower_1_conv_2_relu" + type: "ReLU" + bottom: "mixed_8_tower_1_conv_2_conv2d_bn" + top: "mixed_8_tower_1_conv_2_conv2d_relu" +}layer { + name: "mixed_8_tower_1_conv_3_conv2d" + type: "Convolution" + bottom: "mixed_8_tower_1_conv_2_conv2d_relu" + top: "mixed_8_tower_1_conv_3_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad: 0 + kernel_size: 3 + stride: 2 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_8_tower_1_conv_3_batchnorm" + type: "BatchNorm" + bottom: "mixed_8_tower_1_conv_3_conv2d" + top: "mixed_8_tower_1_conv_3_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_8_tower_1_conv_3_relu" + type: "ReLU" + bottom: "mixed_8_tower_1_conv_3_conv2d_bn" + top: "mixed_8_tower_1_conv_3_conv2d_relu" +}layer { + name: "MAX_pool_mixed_8_pool" + type: "Pooling" + bottom: "ch_concat_mixed_7_chconcat" + top: "MAX_pool_mixed_8_pool" + pooling_param { + pool: MAX + pad: 0 + kernel_size: 3 + stride: 2 + } +} +layer { + name: "ch_concat_mixed_8_chconcat" + bottom: "mixed_8_tower_conv_1_conv2d_relu" +bottom: "mixed_8_tower_1_conv_3_conv2d_relu" +bottom: "MAX_pool_mixed_8_pool" + + top: "ch_concat_mixed_8_chconcat" + type: "Concat" + concat_param { + axis: 1 + } +} +layer { + name: "mixed_9_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_8_chconcat" + top: "mixed_9_conv_conv2d" + convolution_param { + bias_term: false + num_output: 320 + pad: 0 + kernel_h: 1 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_9_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_9_conv_conv2d" + top: "mixed_9_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_9_conv_relu" + type: "ReLU" + bottom: "mixed_9_conv_conv2d_bn" + top: "mixed_9_conv_conv2d_relu" +}layer { + name: "mixed_9_tower_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_8_chconcat" + top: "mixed_9_tower_conv_conv2d" + convolution_param { + bias_term: false + num_output: 384 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_9_tower_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_9_tower_conv_conv2d" + top: "mixed_9_tower_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_9_tower_conv_relu" + type: "ReLU" + bottom: "mixed_9_tower_conv_conv2d_bn" + top: "mixed_9_tower_conv_conv2d_relu" +}layer { + name: "mixed_9_tower_mixed_conv_conv2d" + type: "Convolution" + bottom: "mixed_9_tower_conv_conv2d_relu" + top: "mixed_9_tower_mixed_conv_conv2d" + convolution_param { + bias_term: false + num_output: 384 + pad_h: 0 +pad_w: 1 + kernel_h: 1 +kernel_w: 3 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_9_tower_mixed_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_9_tower_mixed_conv_conv2d" + top: "mixed_9_tower_mixed_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_9_tower_mixed_conv_relu" + type: "ReLU" + bottom: "mixed_9_tower_mixed_conv_conv2d_bn" + top: "mixed_9_tower_mixed_conv_conv2d_relu" +}layer { + name: "mixed_9_tower_mixed_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_9_tower_conv_conv2d_relu" + top: "mixed_9_tower_mixed_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 384 + pad_h: 1 +pad_w: 0 + kernel_h: 3 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_9_tower_mixed_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_9_tower_mixed_conv_1_conv2d" + top: "mixed_9_tower_mixed_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_9_tower_mixed_conv_1_relu" + type: "ReLU" + bottom: "mixed_9_tower_mixed_conv_1_conv2d_bn" + top: "mixed_9_tower_mixed_conv_1_conv2d_relu" +}layer { + name: "mixed_9_tower_1_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_8_chconcat" + top: "mixed_9_tower_1_conv_conv2d" + convolution_param { + bias_term: false + num_output: 448 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_9_tower_1_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_9_tower_1_conv_conv2d" + top: "mixed_9_tower_1_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_9_tower_1_conv_relu" + type: "ReLU" + bottom: "mixed_9_tower_1_conv_conv2d_bn" + top: "mixed_9_tower_1_conv_conv2d_relu" +}layer { + name: "mixed_9_tower_1_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_9_tower_1_conv_conv2d_relu" + top: "mixed_9_tower_1_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 384 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_9_tower_1_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_9_tower_1_conv_1_conv2d" + top: "mixed_9_tower_1_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_9_tower_1_conv_1_relu" + type: "ReLU" + bottom: "mixed_9_tower_1_conv_1_conv2d_bn" + top: "mixed_9_tower_1_conv_1_conv2d_relu" +}layer { + name: "mixed_9_tower_1_mixed_conv_conv2d" + type: "Convolution" + bottom: "mixed_9_tower_1_conv_1_conv2d_relu" + top: "mixed_9_tower_1_mixed_conv_conv2d" + convolution_param { + bias_term: false + num_output: 384 + pad_h: 0 +pad_w: 1 + kernel_h: 1 +kernel_w: 3 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_9_tower_1_mixed_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_9_tower_1_mixed_conv_conv2d" + top: "mixed_9_tower_1_mixed_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_9_tower_1_mixed_conv_relu" + type: "ReLU" + bottom: "mixed_9_tower_1_mixed_conv_conv2d_bn" + top: "mixed_9_tower_1_mixed_conv_conv2d_relu" +}layer { + name: "mixed_9_tower_1_mixed_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_9_tower_1_conv_1_conv2d_relu" + top: "mixed_9_tower_1_mixed_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 384 + pad_h: 1 +pad_w: 0 + kernel_h: 3 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_9_tower_1_mixed_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_9_tower_1_mixed_conv_1_conv2d" + top: "mixed_9_tower_1_mixed_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_9_tower_1_mixed_conv_1_relu" + type: "ReLU" + bottom: "mixed_9_tower_1_mixed_conv_1_conv2d_bn" + top: "mixed_9_tower_1_mixed_conv_1_conv2d_relu" +}layer { + name: "AVE_pool_mixed_9_pool" + type: "Pooling" + bottom: "ch_concat_mixed_8_chconcat" + top: "AVE_pool_mixed_9_pool" + pooling_param { + pool: AVE + pad: 1 + kernel_size: 3 + stride: 1 + } +}layer { + name: "mixed_9_tower_2_conv_conv2d" + type: "Convolution" + bottom: "AVE_pool_mixed_9_pool" + top: "mixed_9_tower_2_conv_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_9_tower_2_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_9_tower_2_conv_conv2d" + top: "mixed_9_tower_2_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_9_tower_2_conv_relu" + type: "ReLU" + bottom: "mixed_9_tower_2_conv_conv2d_bn" + top: "mixed_9_tower_2_conv_conv2d_relu" +} +layer { + name: "ch_concat_mixed_9_chconcat" + bottom: "mixed_9_conv_conv2d_relu" +bottom: "mixed_9_tower_mixed_conv_conv2d_relu" +bottom: "mixed_9_tower_mixed_conv_1_conv2d_relu" +bottom: "mixed_9_tower_1_mixed_conv_conv2d_relu" +bottom: "mixed_9_tower_1_mixed_conv_1_conv2d_relu" +bottom: "mixed_9_tower_2_conv_conv2d_relu" + + top: "ch_concat_mixed_9_chconcat" + type: "Concat" + concat_param { + axis: 1 + } +} +layer { + name: "mixed_10_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_9_chconcat" + top: "mixed_10_conv_conv2d" + convolution_param { + bias_term: false + num_output: 320 + pad: 0 + kernel_h: 1 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_10_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_10_conv_conv2d" + top: "mixed_10_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_10_conv_relu" + type: "ReLU" + bottom: "mixed_10_conv_conv2d_bn" + top: "mixed_10_conv_conv2d_relu" +}layer { + name: "mixed_10_tower_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_9_chconcat" + top: "mixed_10_tower_conv_conv2d" + convolution_param { + bias_term: false + num_output: 384 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_10_tower_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_10_tower_conv_conv2d" + top: "mixed_10_tower_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_10_tower_conv_relu" + type: "ReLU" + bottom: "mixed_10_tower_conv_conv2d_bn" + top: "mixed_10_tower_conv_conv2d_relu" +}layer { + name: "mixed_10_tower_mixed_conv_conv2d" + type: "Convolution" + bottom: "mixed_10_tower_conv_conv2d_relu" + top: "mixed_10_tower_mixed_conv_conv2d" + convolution_param { + bias_term: false + num_output: 384 + pad_h: 0 +pad_w: 1 + kernel_h: 1 +kernel_w: 3 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_10_tower_mixed_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_10_tower_mixed_conv_conv2d" + top: "mixed_10_tower_mixed_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_10_tower_mixed_conv_relu" + type: "ReLU" + bottom: "mixed_10_tower_mixed_conv_conv2d_bn" + top: "mixed_10_tower_mixed_conv_conv2d_relu" +}layer { + name: "mixed_10_tower_mixed_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_10_tower_conv_conv2d_relu" + top: "mixed_10_tower_mixed_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 384 + pad_h: 1 +pad_w: 0 + kernel_h: 3 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_10_tower_mixed_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_10_tower_mixed_conv_1_conv2d" + top: "mixed_10_tower_mixed_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_10_tower_mixed_conv_1_relu" + type: "ReLU" + bottom: "mixed_10_tower_mixed_conv_1_conv2d_bn" + top: "mixed_10_tower_mixed_conv_1_conv2d_relu" +}layer { + name: "mixed_10_tower_1_conv_conv2d" + type: "Convolution" + bottom: "ch_concat_mixed_9_chconcat" + top: "mixed_10_tower_1_conv_conv2d" + convolution_param { + bias_term: false + num_output: 448 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_10_tower_1_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_10_tower_1_conv_conv2d" + top: "mixed_10_tower_1_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_10_tower_1_conv_relu" + type: "ReLU" + bottom: "mixed_10_tower_1_conv_conv2d_bn" + top: "mixed_10_tower_1_conv_conv2d_relu" +}layer { + name: "mixed_10_tower_1_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_10_tower_1_conv_conv2d_relu" + top: "mixed_10_tower_1_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 384 + pad: 1 + kernel_size: 3 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_10_tower_1_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_10_tower_1_conv_1_conv2d" + top: "mixed_10_tower_1_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_10_tower_1_conv_1_relu" + type: "ReLU" + bottom: "mixed_10_tower_1_conv_1_conv2d_bn" + top: "mixed_10_tower_1_conv_1_conv2d_relu" +}layer { + name: "mixed_10_tower_1_mixed_conv_conv2d" + type: "Convolution" + bottom: "mixed_10_tower_1_conv_1_conv2d_relu" + top: "mixed_10_tower_1_mixed_conv_conv2d" + convolution_param { + bias_term: false + num_output: 384 + pad_h: 0 +pad_w: 1 + kernel_h: 1 +kernel_w: 3 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_10_tower_1_mixed_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_10_tower_1_mixed_conv_conv2d" + top: "mixed_10_tower_1_mixed_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_10_tower_1_mixed_conv_relu" + type: "ReLU" + bottom: "mixed_10_tower_1_mixed_conv_conv2d_bn" + top: "mixed_10_tower_1_mixed_conv_conv2d_relu" +}layer { + name: "mixed_10_tower_1_mixed_conv_1_conv2d" + type: "Convolution" + bottom: "mixed_10_tower_1_conv_1_conv2d_relu" + top: "mixed_10_tower_1_mixed_conv_1_conv2d" + convolution_param { + bias_term: false + num_output: 384 + pad_h: 1 +pad_w: 0 + kernel_h: 3 +kernel_w: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_10_tower_1_mixed_conv_1_batchnorm" + type: "BatchNorm" + bottom: "mixed_10_tower_1_mixed_conv_1_conv2d" + top: "mixed_10_tower_1_mixed_conv_1_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_10_tower_1_mixed_conv_1_relu" + type: "ReLU" + bottom: "mixed_10_tower_1_mixed_conv_1_conv2d_bn" + top: "mixed_10_tower_1_mixed_conv_1_conv2d_relu" +}layer { + name: "MAX_pool_mixed_10_pool" + type: "Pooling" + bottom: "ch_concat_mixed_9_chconcat" + top: "MAX_pool_mixed_10_pool" + pooling_param { + pool: MAX + pad: 1 + kernel_size: 3 + stride: 1 + } +}layer { + name: "mixed_10_tower_2_conv_conv2d" + type: "Convolution" + bottom: "MAX_pool_mixed_10_pool" + top: "mixed_10_tower_2_conv_conv2d" + convolution_param { + bias_term: false + num_output: 192 + pad: 0 + kernel_size: 1 + stride: 1 + weight_filler { + type: "xavier" + } + } + +}layer { + name: "mixed_10_tower_2_conv_batchnorm" + type: "BatchNorm" + bottom: "mixed_10_tower_2_conv_conv2d" + top: "mixed_10_tower_2_conv_conv2d_bn" + batch_norm_param { + use_global_stats: false + eps: 0.001 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } + param { + lr_mult: 0 + } +}layer { + name: "mixed_10_tower_2_conv_relu" + type: "ReLU" + bottom: "mixed_10_tower_2_conv_conv2d_bn" + top: "mixed_10_tower_2_conv_conv2d_relu" +} +layer { + name: "ch_concat_mixed_10_chconcat" + bottom: "mixed_10_conv_conv2d_relu" +bottom: "mixed_10_tower_mixed_conv_conv2d_relu" +bottom: "mixed_10_tower_mixed_conv_1_conv2d_relu" +bottom: "mixed_10_tower_1_mixed_conv_conv2d_relu" +bottom: "mixed_10_tower_1_mixed_conv_1_conv2d_relu" +bottom: "mixed_10_tower_2_conv_conv2d_relu" + + top: "ch_concat_mixed_10_chconcat" + type: "Concat" + concat_param { + axis: 1 + } +} +layer { + name: "global_pool" + type: "Pooling" + bottom: "ch_concat_mixed_10_chconcat" + top: "global_pool" + pooling_param { + pool: AVE + pad: 0 + kernel_size: 8 + stride: 1 + } +} +layer { + name: "flatten" + type: "Flatten" + bottom: "global_pool" + top: "flatten" +}layer { + name: "fc1" + type: "InnerProduct" + bottom: "flatten" + top: "fc1" + param { + lr_mult: 1 + decay_mult: 1 + } + param { + lr_mult: 2 + decay_mult: 0 + } + inner_product_param { + num_output: 1000 + weight_filler { + type: "xavier" + } + bias_filler { + type: "constant" + value: 0 + } + } +}layer { + name: "loss" + type: "SoftmaxWithLoss" + bottom: "fc1" + bottom: "label" + top: "loss" +} +layer { + name: "acc/top-1" + type: "Accuracy" + bottom: "fc1" + bottom: "label" + top: "acc/top-1" + include { + phase: TEST + } +} +layer { + name: "acc/top-5" + type: "Accuracy" + bottom: "fc1" + bottom: "label" + top: "acc/top-5" + include { + phase: TEST + } + accuracy_param { + top_k: 5 + } +} \ No newline at end of file diff --git a/presets/squeezenet.prototxt b/presets/squeezenet.prototxt new file mode 100755 index 0000000..3adae9b --- /dev/null +++ b/presets/squeezenet.prototxt @@ -0,0 +1,637 @@ +name: "SqueezeNet" +layer { + name: "data" + type: "Data" + top: "data" + input_param { + shape: { + dim: 1 + dim: 3 + dim: 224 + dim: 227 + } + } +} +layer { + name: "conv1" + type: "Convolution" + bottom: "data" + top: "conv1" + convolution_param { + num_output: 96 + kernel_size: 7 + stride: 2 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "relu_conv1" + type: "ReLU" + bottom: "conv1" + top: "conv1" +} +layer { + name: "pool1" + type: "Pooling" + bottom: "conv1" + top: "pool1" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 2 + } +} +layer { + name: "fire2/squeeze1x1" + type: "Convolution" + bottom: "pool1" + top: "fire2/squeeze1x1" + convolution_param { + num_output: 16 + kernel_size: 1 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire2/relu_squeeze1x1" + type: "ReLU" + bottom: "fire2/squeeze1x1" + top: "fire2/squeeze1x1" +} +layer { + name: "fire2/expand1x1" + type: "Convolution" + bottom: "fire2/squeeze1x1" + top: "fire2/expand1x1" + convolution_param { + num_output: 64 + kernel_size: 1 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire2/relu_expand1x1" + type: "ReLU" + bottom: "fire2/expand1x1" + top: "fire2/expand1x1" +} +layer { + name: "fire2/expand3x3" + type: "Convolution" + bottom: "fire2/squeeze1x1" + top: "fire2/expand3x3" + convolution_param { + num_output: 64 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire2/relu_expand3x3" + type: "ReLU" + bottom: "fire2/expand3x3" + top: "fire2/expand3x3" +} +layer { + name: "fire2/concat" + type: "Concat" + bottom: "fire2/expand1x1" + bottom: "fire2/expand3x3" + top: "fire2/concat" +} +layer { + name: "fire3/squeeze1x1" + type: "Convolution" + bottom: "fire2/concat" + top: "fire3/squeeze1x1" + convolution_param { + num_output: 16 + kernel_size: 1 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire3/relu_squeeze1x1" + type: "ReLU" + bottom: "fire3/squeeze1x1" + top: "fire3/squeeze1x1" +} +layer { + name: "fire3/expand1x1" + type: "Convolution" + bottom: "fire3/squeeze1x1" + top: "fire3/expand1x1" + convolution_param { + num_output: 64 + kernel_size: 1 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire3/relu_expand1x1" + type: "ReLU" + bottom: "fire3/expand1x1" + top: "fire3/expand1x1" +} +layer { + name: "fire3/expand3x3" + type: "Convolution" + bottom: "fire3/squeeze1x1" + top: "fire3/expand3x3" + convolution_param { + num_output: 64 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire3/relu_expand3x3" + type: "ReLU" + bottom: "fire3/expand3x3" + top: "fire3/expand3x3" +} +layer { + name: "fire3/concat" + type: "Concat" + bottom: "fire3/expand1x1" + bottom: "fire3/expand3x3" + top: "fire3/concat" +} +layer { + name: "fire4/squeeze1x1" + type: "Convolution" + bottom: "fire3/concat" + top: "fire4/squeeze1x1" + convolution_param { + num_output: 32 + kernel_size: 1 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire4/relu_squeeze1x1" + type: "ReLU" + bottom: "fire4/squeeze1x1" + top: "fire4/squeeze1x1" +} +layer { + name: "fire4/expand1x1" + type: "Convolution" + bottom: "fire4/squeeze1x1" + top: "fire4/expand1x1" + convolution_param { + num_output: 128 + kernel_size: 1 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire4/relu_expand1x1" + type: "ReLU" + bottom: "fire4/expand1x1" + top: "fire4/expand1x1" +} +layer { + name: "fire4/expand3x3" + type: "Convolution" + bottom: "fire4/squeeze1x1" + top: "fire4/expand3x3" + convolution_param { + num_output: 128 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire4/relu_expand3x3" + type: "ReLU" + bottom: "fire4/expand3x3" + top: "fire4/expand3x3" +} +layer { + name: "fire4/concat" + type: "Concat" + bottom: "fire4/expand1x1" + bottom: "fire4/expand3x3" + top: "fire4/concat" +} +layer { + name: "pool4" + type: "Pooling" + bottom: "fire4/concat" + top: "pool4" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 2 + } +} +layer { + name: "fire5/squeeze1x1" + type: "Convolution" + bottom: "pool4" + top: "fire5/squeeze1x1" + convolution_param { + num_output: 32 + kernel_size: 1 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire5/relu_squeeze1x1" + type: "ReLU" + bottom: "fire5/squeeze1x1" + top: "fire5/squeeze1x1" +} +layer { + name: "fire5/expand1x1" + type: "Convolution" + bottom: "fire5/squeeze1x1" + top: "fire5/expand1x1" + convolution_param { + num_output: 128 + kernel_size: 1 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire5/relu_expand1x1" + type: "ReLU" + bottom: "fire5/expand1x1" + top: "fire5/expand1x1" +} +layer { + name: "fire5/expand3x3" + type: "Convolution" + bottom: "fire5/squeeze1x1" + top: "fire5/expand3x3" + convolution_param { + num_output: 128 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire5/relu_expand3x3" + type: "ReLU" + bottom: "fire5/expand3x3" + top: "fire5/expand3x3" +} +layer { + name: "fire5/concat" + type: "Concat" + bottom: "fire5/expand1x1" + bottom: "fire5/expand3x3" + top: "fire5/concat" +} +layer { + name: "fire6/squeeze1x1" + type: "Convolution" + bottom: "fire5/concat" + top: "fire6/squeeze1x1" + convolution_param { + num_output: 48 + kernel_size: 1 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire6/relu_squeeze1x1" + type: "ReLU" + bottom: "fire6/squeeze1x1" + top: "fire6/squeeze1x1" +} +layer { + name: "fire6/expand1x1" + type: "Convolution" + bottom: "fire6/squeeze1x1" + top: "fire6/expand1x1" + convolution_param { + num_output: 192 + kernel_size: 1 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire6/relu_expand1x1" + type: "ReLU" + bottom: "fire6/expand1x1" + top: "fire6/expand1x1" +} +layer { + name: "fire6/expand3x3" + type: "Convolution" + bottom: "fire6/squeeze1x1" + top: "fire6/expand3x3" + convolution_param { + num_output: 192 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire6/relu_expand3x3" + type: "ReLU" + bottom: "fire6/expand3x3" + top: "fire6/expand3x3" +} +layer { + name: "fire6/concat" + type: "Concat" + bottom: "fire6/expand1x1" + bottom: "fire6/expand3x3" + top: "fire6/concat" +} +layer { + name: "fire7/squeeze1x1" + type: "Convolution" + bottom: "fire6/concat" + top: "fire7/squeeze1x1" + convolution_param { + num_output: 48 + kernel_size: 1 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire7/relu_squeeze1x1" + type: "ReLU" + bottom: "fire7/squeeze1x1" + top: "fire7/squeeze1x1" +} +layer { + name: "fire7/expand1x1" + type: "Convolution" + bottom: "fire7/squeeze1x1" + top: "fire7/expand1x1" + convolution_param { + num_output: 192 + kernel_size: 1 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire7/relu_expand1x1" + type: "ReLU" + bottom: "fire7/expand1x1" + top: "fire7/expand1x1" +} +layer { + name: "fire7/expand3x3" + type: "Convolution" + bottom: "fire7/squeeze1x1" + top: "fire7/expand3x3" + convolution_param { + num_output: 192 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire7/relu_expand3x3" + type: "ReLU" + bottom: "fire7/expand3x3" + top: "fire7/expand3x3" +} +layer { + name: "fire7/concat" + type: "Concat" + bottom: "fire7/expand1x1" + bottom: "fire7/expand3x3" + top: "fire7/concat" +} +layer { + name: "fire8/squeeze1x1" + type: "Convolution" + bottom: "fire7/concat" + top: "fire8/squeeze1x1" + convolution_param { + num_output: 64 + kernel_size: 1 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire8/relu_squeeze1x1" + type: "ReLU" + bottom: "fire8/squeeze1x1" + top: "fire8/squeeze1x1" +} +layer { + name: "fire8/expand1x1" + type: "Convolution" + bottom: "fire8/squeeze1x1" + top: "fire8/expand1x1" + convolution_param { + num_output: 256 + kernel_size: 1 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire8/relu_expand1x1" + type: "ReLU" + bottom: "fire8/expand1x1" + top: "fire8/expand1x1" +} +layer { + name: "fire8/expand3x3" + type: "Convolution" + bottom: "fire8/squeeze1x1" + top: "fire8/expand3x3" + convolution_param { + num_output: 256 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire8/relu_expand3x3" + type: "ReLU" + bottom: "fire8/expand3x3" + top: "fire8/expand3x3" +} +layer { + name: "fire8/concat" + type: "Concat" + bottom: "fire8/expand1x1" + bottom: "fire8/expand3x3" + top: "fire8/concat" +} +layer { + name: "pool8" + type: "Pooling" + bottom: "fire8/concat" + top: "pool8" + pooling_param { + pool: MAX + kernel_size: 3 + stride: 2 + } +} +layer { + name: "fire9/squeeze1x1" + type: "Convolution" + bottom: "pool8" + top: "fire9/squeeze1x1" + convolution_param { + num_output: 64 + kernel_size: 1 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire9/relu_squeeze1x1" + type: "ReLU" + bottom: "fire9/squeeze1x1" + top: "fire9/squeeze1x1" +} +layer { + name: "fire9/expand1x1" + type: "Convolution" + bottom: "fire9/squeeze1x1" + top: "fire9/expand1x1" + convolution_param { + num_output: 256 + kernel_size: 1 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire9/relu_expand1x1" + type: "ReLU" + bottom: "fire9/expand1x1" + top: "fire9/expand1x1" +} +layer { + name: "fire9/expand3x3" + type: "Convolution" + bottom: "fire9/squeeze1x1" + top: "fire9/expand3x3" + convolution_param { + num_output: 256 + pad: 1 + kernel_size: 3 + weight_filler { + type: "xavier" + } + } +} +layer { + name: "fire9/relu_expand3x3" + type: "ReLU" + bottom: "fire9/expand3x3" + top: "fire9/expand3x3" +} +layer { + name: "fire9/concat" + type: "Concat" + bottom: "fire9/expand1x1" + bottom: "fire9/expand3x3" + top: "fire9/concat" +} +layer { + name: "drop9" + type: "Dropout" + bottom: "fire9/concat" + top: "fire9/concat" + dropout_param { + dropout_ratio: 0.5 + } +} +layer { + name: "conv10" + type: "Convolution" + bottom: "fire9/concat" + top: "conv10" + convolution_param { + num_output: 1000 + pad: 1 + kernel_size: 1 + weight_filler { + type: "gaussian" + mean: 0.0 + std: 0.01 + } + } +} +layer { + name: "relu_conv10" + type: "ReLU" + bottom: "conv10" + top: "conv10" +} +layer { + name: "pool10" + type: "Pooling" + bottom: "conv10" + top: "pool10" + pooling_param { + pool: AVE + global_pooling: true + } +} +layer { + name: "loss" + type: "SoftmaxWithLoss" + bottom: "pool10" + top: "loss" + include { + phase: TRAIN + } +} \ No newline at end of file diff --git a/quickstart.html b/quickstart.html index 77c45e4..da62c5e 100644 --- a/quickstart.html +++ b/quickstart.html @@ -10,11 +10,12 @@

Netscope

A web-based tool for visualizing neural network architectures (or technically, any directed acyclic graph). It currently supports Caffe's prototxt format.

+

Originally developed by ethereon. Extended for CNN analysis by dgschwend

Gist Support

If your .prototxt file is part of a GitHub Gist, you can visualize it by visiting this URL:

-
http://ethereon.github.io/netscope/#/gist/your-gist-id
+
http://dgschwend.github.io/netscope/#/gist/your-gist-id

The Gist ID is the numeric suffix in the Gist's URL.

View Example
@@ -26,6 +27,14 @@

Editor

Presets

+
+ SqueezeNet +
Forrest Iandola, Matthew Moskewicz, Khalid Ashraf, Song Han, William Dally, Kurt Keutzer
+
+
+ Inception v3 +
Christian Szegedy, Vincent Vanhoucke, Sergey Ioffe, Jonathon Shlens, Zbigniew Wojna
+
AlexNet
Alex Krizhevsky, Ilya Sutskever, Geoffry Hinton
diff --git a/src/app.coffee b/src/app.coffee index fc00e97..342d683 100644 --- a/src/app.coffee +++ b/src/app.coffee @@ -9,6 +9,8 @@ class AppController @$netBox = $('#net-container') @$netError = $('#net-error') @svg = '#net-svg' + @$tableBox = $('#table-container') + @table = '#table-content' @setupErrorHandler() startLoading: (loader, args...) -> @@ -16,6 +18,7 @@ class AppController return @$netError.hide() @$netBox.hide() + @$tableBox.hide() @$spinner.show() loader args..., (net) => @completeLoading(net) @@ -23,9 +26,10 @@ class AppController @$spinner.hide() $('#net-title').html(net.name.replace(/_/g, ' ')) @$netBox.show() + @$tableBox.show() $(@svg).empty() $('.qtip').remove() - renderer = new Renderer net, @svg + renderer = new Renderer net, @svg, @table @inProgress = false makeLoader: (loader) -> diff --git a/src/caffe/caffe.coffee b/src/caffe/caffe.coffee index bfae538..37d7f0c 100644 --- a/src/caffe/caffe.coffee +++ b/src/caffe/caffe.coffee @@ -20,6 +20,86 @@ generateLayers = (descriptors, phase) -> not (layerPhase? and layerPhase!=phase) return layers +analyzeNetwork = (net) -> + ## Add Input/Output Dimensions + Channels to each Node / Layer + #shape.dim: ( N x K x W x H ) + # batch channel width height + + for n in net.nodes + # init to zero + d = n.dim + d.wIn = d.hIn = d.wOut = d.hOut = 0 + d.featIn = d.featOut = 0 + + prev = n.parents[0]?.dim + layertype = n.type.toUpperCase() + switch layertype + when "DATA" + d.featIn = n.attribs.input_param.shape.dim[1] + d.featOut = d.featIn + d.wIn = n.attribs.input_param.shape.dim[2] + d.hIn = n.attribs.input_param.shape.dim[3] + d.wOut = d.wIn; d.hOut = d.hIn + + when "CONVOLUTION" + kernel = n.attribs.convolution_param.kernel_size + stride = n.attribs.convolution_param.stride ? 1 + pad = n.attribs.convolution_param.pad ? 0 + numout = n.attribs.convolution_param.num_output + d.wIn = prev.wOut; d.hIn = prev.hOut + # according to http://caffe.berkeleyvision.org/tutorial/layers.html + d.wOut = ((d.wIn + 2*pad - kernel) / stride + 1) + d.hOut = ((d.hIn + 2*pad - kernel) / stride + 1) + d.featIn = prev.featOut + d.featOut = numout + + when "POOLING" + kernel = n.attribs.pooling_param.kernel_size + stride = n.attribs.pooling_param.stride ? 1 + pad = n.attribs.pooling_param.pad ? 0 + isglobal = n.attribs.pooling_param.global_pooling ? 0 + d.wIn = prev.wOut; d.hIn = prev.hOut + # according to http://caffe.berkeleyvision.org/tutorial/layers.html + if !isglobal + d.wOut = ((d.wIn + 2*pad - kernel) / stride + 1) + d.hOut = ((d.hIn + 2*pad - kernel) / stride + 1) + else + d.wOut = d.hOut = 1 + + d.featIn = prev.featOut + d.featOut = d.featIn + + when "CONCAT" + d.wIn = prev.wOut; d.hIn = prev.hOut + d.wOut = d.wIn; d.hOut = d.hIn + + # check all input dims agree + dims_ok = true + dims_ok = dims_ok && (p.dim.wOut == d.wIn & p.dim.hOut == d.hIn) for p in n.parents + console.warn('CONCAT: input dimensions dont agree!') if not dims_ok + + # sum up channels from inputs + d.featIn += p.dim.featOut for p in n.parents + d.featOut = d.featIn + + else # RELU or unknown layer; Out Dim = In Dim + d.wIn = prev?.wOut; + d.hIn = prev?.hOut + d.wOut = d.wIn; d.hOut = d.hIn + d.featIn = prev?.featOut + d.featOut = d.featIn + + # add dimensions to node attributes + # so they show in graph tooltips + if (layertype!="RELU" && layertype!="SOFTMAX" && layertype!="SOFTMAXWITHLOSS") + _.extend(n.attribs, { + analysis: { + in: d.featIn+'ch ⋅ '+d.wIn+'×'+d.hIn, + out: d.featOut+'ch ⋅ '+d.wOut+'×'+d.hOut + }} ) + + return net + generateNetwork = (layers, header) -> nodeTable = {} implicitLayers = [] @@ -39,7 +119,7 @@ generateNetwork = (layers, header) -> _.map names, getSingleNode # Build the node LUT. for layer in layers - nodeTable[layer.name] = net.createNode layer.name, layer.type, layer.attribs + nodeTable[layer.name] = net.createNode layer.name, layer.type, layer.attribs, {} # Connect layers. inplaceTable = {} for layer in layers @@ -71,7 +151,7 @@ generateNetwork = (layers, header) -> if header?.input? and header?.input_dim? inputs = [].concat header.input dims = header.input_dim - if inputs.length==(dims.length/4) + if inputs.length==(dims.length*0.25) for input, i in inputs dataNode = nodeTable[input] dataNode.type = 'data' @@ -85,4 +165,6 @@ class CaffeParser @parse : (txt, phase) -> [header, layerDesc] = Parser.parse txt layers = generateLayers layerDesc, phase - return generateNetwork layers, header + network = generateNetwork layers, header + network = analyzeNetwork network + return network diff --git a/src/netscope.coffee b/src/netscope.coffee index 586e958..7a3eaf3 100644 --- a/src/netscope.coffee +++ b/src/netscope.coffee @@ -24,4 +24,4 @@ $(document).ready -> '/editor(/?)' : => app.showEditor loader '/doc' : => showDocumentation() router = Router(routes) - router.init '/doc' + router.init '/doc' \ No newline at end of file diff --git a/src/network.coffee b/src/network.coffee index bdb6736..62c3d49 100644 --- a/src/network.coffee +++ b/src/network.coffee @@ -1,5 +1,5 @@ class Node - constructor: (@name, @type, @attribs={}) -> + constructor: (@name, @type, @attribs={}, @dim={}) -> @parents = [] @children = [] # Nodes to be coalesced (by the renderer) with the current one. @@ -37,8 +37,8 @@ class Network constructor: (@name='Untitled Network') -> @nodes = [] - createNode: (label, type, attribs) -> - node = new Node label, type, attribs + createNode: (label, type, attribs, dim) -> + node = new Node label, type, attribs, dim @nodes.push node return node diff --git a/src/renderer.coffee b/src/renderer.coffee index cffeed0..13db50d 100644 --- a/src/renderer.coffee +++ b/src/renderer.coffee @@ -1,16 +1,20 @@ +tableify = require('tableify') +require('tablesorter') + module.exports = class Renderer - constructor: (@net, @parent) -> + constructor: (@net, @parent, @table) -> @iconify = false @layoutDirection = 'tb' @generateGraph() + @renderTable() setupGraph: -> @graph = new dagreD3.graphlib.Graph() @graph.setDefaultEdgeLabel ( -> {} ) @graph.setGraph rankdir: @layoutDirection - ranksep: 30, # Vertical node separation + ranksep: 20, # Vertical node separation nodesep: 10, # Horizontal node separation edgesep: 20, # Horizontal edge separation marginx: 0, # Horizontal graph margin @@ -39,6 +43,50 @@ class Renderer for sink in @graph.sinks() (@graph.node sink).class = 'node-type-sink' @render() + + generateTable: -> + entry = {name: 'start'} + tbl = [] + id = 0 + for n in @net.sortTopologically() + id++ + entry = { + ID: id + name: n.name + type: n.type + ch_in: n.dim.featIn + dim_in: n.dim.wIn+'x'+n.dim.hIn + ch_out: n.dim.featOut + dim_out: n.dim.wOut+'x'+n.dim.hOut + } + tbl.push(entry) + return tbl + + summarizeTable: (tbl) -> + entry = {name: 'start'} + summary = [] + for n in tbl + submodule = n.name.indexOf('/') + if (submodule>0 and entry.name.substring(0,submodule) == n.name.substring(0,submodule)) + entry.ID += '.' + entry.name = n.name.substring(0,submodule) + entry.type = 'submodule' + entry.ch_out = n.ch_out + entry.dim_out = n.dim_out + summary.pop() + summary.push(entry) + else + entry = n + summary.push(entry) + return summary + + renderTable: -> + tbl = @generateTable() + summary = @summarizeTable(tbl) + $(@table).html('

Summary:

'+tableify(summary)+ + '

Details:

'+tableify(tbl)); + $(@table+' table').tablesorter() + insertNode: (layers) -> baseNode = layers[0] @@ -47,13 +95,13 @@ class Renderer for layer in layers layer.isInGraph = true nodeLabel += @generateLabel layer - nodeDesc = - labelType : 'html' - label : nodeLabel - class : nodeClass - layers : layers - rx : 5 - ry : 5 + nodeDesc = + labelType : 'html' + label : nodeLabel + class : nodeClass + layers : layers + rx : 5 + ry : 5 if @iconify _.extend nodeDesc, shape: 'circle' @@ -66,8 +114,9 @@ class Renderer '' insertLink: (src, dst) -> - @graph.setEdge src.name, dst.name, - arrowhead : 'vee' + lbl = src.dim.featOut+'ch ⋅ '+src.dim.wOut+'×'+src.dim.hOut + @graph.setEdge(src.name, dst.name, + { arrowhead: 'vee', label: lbl } ); renderKey:(key) -> key.replace(/_/g, ' ') From afc3b8a0ef27e1d35a9ce7f5912f351d6eb0ba5f Mon Sep 17 00:00:00 2001 From: David Gschwend Date: Sun, 20 Mar 2016 13:59:58 +0100 Subject: [PATCH 04/77] update compiled JS --- assets/js/netscope.js | 15824 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 15801 insertions(+), 23 deletions(-) diff --git a/assets/js/netscope.js b/assets/js/netscope.js index 8df4e59..6909f21 100644 --- a/assets/js/netscope.js +++ b/assets/js/netscope.js @@ -13,6 +13,8 @@ module.exports = AppController = (function() { this.$netBox = $('#net-container'); this.$netError = $('#net-error'); this.svg = '#net-svg'; + this.$tableBox = $('#table-container'); + this.table = '#table-content'; this.setupErrorHandler(); } @@ -24,6 +26,7 @@ module.exports = AppController = (function() { } this.$netError.hide(); this.$netBox.hide(); + this.$tableBox.hide(); this.$spinner.show(); return loader.apply(null, slice.call(args).concat([(function(_this) { return function(net) { @@ -37,9 +40,10 @@ module.exports = AppController = (function() { this.$spinner.hide(); $('#net-title').html(net.name.replace(/_/g, ' ')); this.$netBox.show(); + this.$tableBox.show(); $(this.svg).empty(); $('.qtip').remove(); - renderer = new Renderer(net, this.svg); + renderer = new Renderer(net, this.svg, this.table); return this.inProgress = false; }; @@ -85,7 +89,7 @@ module.exports = AppController = (function() { },{"./editor.coffee":4,"./renderer.coffee":8}],2:[function(require,module,exports){ -var CaffeParser, Network, Parser, generateLayers, generateNetwork, +var CaffeParser, Network, Parser, analyzeNetwork, generateLayers, generateNetwork, hasProp = {}.hasOwnProperty; Parser = require('./parser'); @@ -119,8 +123,96 @@ generateLayers = function(descriptors, phase) { return layers; }; +analyzeNetwork = function(net) { + var d, dims_ok, isglobal, j, kernel, l, layertype, len, len1, len2, m, n, numout, p, pad, prev, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, ref8, stride; + ref = net.nodes; + for (j = 0, len = ref.length; j < len; j++) { + n = ref[j]; + d = n.dim; + d.wIn = d.hIn = d.wOut = d.hOut = 0; + d.featIn = d.featOut = 0; + prev = (ref1 = n.parents[0]) != null ? ref1.dim : void 0; + layertype = n.type.toUpperCase(); + switch (layertype) { + case "DATA": + d.featIn = n.attribs.input_param.shape.dim[1]; + d.featOut = d.featIn; + d.wIn = n.attribs.input_param.shape.dim[2]; + d.hIn = n.attribs.input_param.shape.dim[3]; + d.wOut = d.wIn; + d.hOut = d.hIn; + break; + case "CONVOLUTION": + kernel = n.attribs.convolution_param.kernel_size; + stride = (ref2 = n.attribs.convolution_param.stride) != null ? ref2 : 1; + pad = (ref3 = n.attribs.convolution_param.pad) != null ? ref3 : 0; + numout = n.attribs.convolution_param.num_output; + d.wIn = prev.wOut; + d.hIn = prev.hOut; + d.wOut = (d.wIn + 2 * pad - kernel) / stride + 1; + d.hOut = (d.hIn + 2 * pad - kernel) / stride + 1; + d.featIn = prev.featOut; + d.featOut = numout; + break; + case "POOLING": + kernel = n.attribs.pooling_param.kernel_size; + stride = (ref4 = n.attribs.pooling_param.stride) != null ? ref4 : 1; + pad = (ref5 = n.attribs.pooling_param.pad) != null ? ref5 : 0; + isglobal = (ref6 = n.attribs.pooling_param.global_pooling) != null ? ref6 : 0; + d.wIn = prev.wOut; + d.hIn = prev.hOut; + if (!isglobal) { + d.wOut = (d.wIn + 2 * pad - kernel) / stride + 1; + d.hOut = (d.hIn + 2 * pad - kernel) / stride + 1; + } else { + d.wOut = d.hOut = 1; + } + d.featIn = prev.featOut; + d.featOut = d.featIn; + break; + case "CONCAT": + d.wIn = prev.wOut; + d.hIn = prev.hOut; + d.wOut = d.wIn; + d.hOut = d.hIn; + dims_ok = true; + ref7 = n.parents; + for (l = 0, len1 = ref7.length; l < len1; l++) { + p = ref7[l]; + dims_ok = dims_ok && (p.dim.wOut === d.wIn & p.dim.hOut === d.hIn); + } + if (!dims_ok) { + console.warn('CONCAT: input dimensions dont agree!'); + } + ref8 = n.parents; + for (m = 0, len2 = ref8.length; m < len2; m++) { + p = ref8[m]; + d.featIn += p.dim.featOut; + } + d.featOut = d.featIn; + break; + default: + d.wIn = prev != null ? prev.wOut : void 0; + d.hIn = prev != null ? prev.hOut : void 0; + d.wOut = d.wIn; + d.hOut = d.hIn; + d.featIn = prev != null ? prev.featOut : void 0; + d.featOut = d.featIn; + } + if (layertype !== "RELU" && layertype !== "SOFTMAX" && layertype !== "SOFTMAXWITHLOSS") { + _.extend(n.attribs, { + analysis: { + "in": d.featIn + 'ch ⋅ ' + d.wIn + '×' + d.hIn, + out: d.featOut + 'ch ⋅ ' + d.wOut + '×' + d.hOut + } + }); + } + } + return net; +}; + generateNetwork = function(layers, header) { - var children, curNode, dataNode, dims, getNodes, getSingleNode, i, implicitLayers, inplaceChild, inplaceOps, inplaceTable, input, inputs, j, k, l, layer, len, len1, len2, len3, m, n, net, node, nodeTable; + var children, curNode, dataNode, dims, getNodes, getSingleNode, i, implicitLayers, inplaceChild, inplaceOps, inplaceTable, input, inputs, j, k, l, layer, len, len1, len2, len3, m, net, node, nodeTable, o; nodeTable = {}; implicitLayers = []; net = new Network(header.name); @@ -146,7 +238,7 @@ generateNetwork = function(layers, header) { })(this); for (j = 0, len = layers.length; j < len; j++) { layer = layers[j]; - nodeTable[layer.name] = net.createNode(layer.name, layer.type, layer.attribs); + nodeTable[layer.name] = net.createNode(layer.name, layer.type, layer.attribs, {}); } inplaceTable = {}; for (l = 0, len1 = layers.length; l < len1; l++) { @@ -184,8 +276,8 @@ generateNetwork = function(layers, header) { if (((header != null ? header.input : void 0) != null) && ((header != null ? header.input_dim : void 0) != null)) { inputs = [].concat(header.input); dims = header.input_dim; - if (inputs.length === (dims.length / 4)) { - for (i = n = 0, len3 = inputs.length; n < len3; i = ++n) { + if (inputs.length === (dims.length * 0.25)) { + for (i = o = 0, len3 = inputs.length; o < len3; i = ++o) { input = inputs[i]; dataNode = nodeTable[input]; dataNode.type = 'data'; @@ -202,10 +294,12 @@ module.exports = CaffeParser = (function() { function CaffeParser() {} CaffeParser.parse = function(txt, phase) { - var header, layerDesc, layers, ref; + var header, layerDesc, layers, network, ref; ref = Parser.parse(txt), header = ref[0], layerDesc = ref[1]; layers = generateLayers(layerDesc, phase); - return generateNetwork(layers, header); + network = generateNetwork(layers, header); + network = analyzeNetwork(network); + return network; }; return CaffeParser; @@ -2042,10 +2136,11 @@ var Network, Node, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; Node = (function() { - function Node(name, type1, attribs1) { + function Node(name, type1, attribs1, dim1) { this.name = name; this.type = type1; this.attribs = attribs1 != null ? attribs1 : {}; + this.dim = dim1 != null ? dim1 : {}; this.detachChildren = bind(this.detachChildren, this); this.detachChild = bind(this.detachChild, this); this.addParents = bind(this.addParents, this); @@ -2113,9 +2208,9 @@ module.exports = Network = (function() { this.nodes = []; } - Network.prototype.createNode = function(label, type, attribs) { + Network.prototype.createNode = function(label, type, attribs, dim) { var node; - node = new Node(label, type, attribs); + node = new Node(label, type, attribs, dim); this.nodes.push(node); return node; }; @@ -2165,16 +2260,22 @@ module.exports = Network = (function() { },{}],8:[function(require,module,exports){ -var Renderer, +var Renderer, tableify, hasProp = {}.hasOwnProperty; +tableify = require('tableify'); + +require('tablesorter'); + module.exports = Renderer = (function() { - function Renderer(net, parent1) { + function Renderer(net, parent1, table) { this.net = net; this.parent = parent1; + this.table = table; this.iconify = false; this.layoutDirection = 'tb'; this.generateGraph(); + this.renderTable(); } Renderer.prototype.setupGraph = function() { @@ -2184,7 +2285,7 @@ module.exports = Renderer = (function() { })); return this.graph.setGraph({ rankdir: this.layoutDirection, - ranksep: 30, + ranksep: 20, nodesep: 10, edgesep: 20, marginx: 0, @@ -2232,6 +2333,64 @@ module.exports = Renderer = (function() { return this.render(); }; + Renderer.prototype.generateTable = function() { + var entry, i, id, len, n, ref, tbl; + entry = { + name: 'start' + }; + tbl = []; + id = 0; + ref = this.net.sortTopologically(); + for (i = 0, len = ref.length; i < len; i++) { + n = ref[i]; + id++; + entry = { + ID: id, + name: n.name, + type: n.type, + ch_in: n.dim.featIn, + dim_in: n.dim.wIn + 'x' + n.dim.hIn, + ch_out: n.dim.featOut, + dim_out: n.dim.wOut + 'x' + n.dim.hOut + }; + tbl.push(entry); + } + return tbl; + }; + + Renderer.prototype.summarizeTable = function(tbl) { + var entry, i, len, n, submodule, summary; + entry = { + name: 'start' + }; + summary = []; + for (i = 0, len = tbl.length; i < len; i++) { + n = tbl[i]; + submodule = n.name.indexOf('/'); + if (submodule > 0 && entry.name.substring(0, submodule) === n.name.substring(0, submodule)) { + entry.ID += '.'; + entry.name = n.name.substring(0, submodule); + entry.type = 'submodule'; + entry.ch_out = n.ch_out; + entry.dim_out = n.dim_out; + summary.pop(); + summary.push(entry); + } else { + entry = n; + summary.push(entry); + } + } + return summary; + }; + + Renderer.prototype.renderTable = function() { + var summary, tbl; + tbl = this.generateTable(); + summary = this.summarizeTable(tbl); + $(this.table).html('

Summary:

' + tableify(summary) + '

Details:

' + tableify(tbl)); + return $(this.table + ' table').tablesorter(); + }; + Renderer.prototype.insertNode = function(layers) { var baseNode, i, layer, len, nodeClass, nodeDesc, nodeLabel; baseNode = layers[0]; @@ -2241,15 +2400,15 @@ module.exports = Renderer = (function() { layer = layers[i]; layer.isInGraph = true; nodeLabel += this.generateLabel(layer); + nodeDesc = { + labelType: 'html', + label: nodeLabel, + "class": nodeClass, + layers: layers, + rx: 5, + ry: 5 + }; } - nodeDesc = { - labelType: 'html', - label: nodeLabel, - "class": nodeClass, - layers: layers, - rx: 5, - ry: 5 - }; if (this.iconify) { _.extend(nodeDesc, { shape: 'circle' @@ -2267,8 +2426,11 @@ module.exports = Renderer = (function() { }; Renderer.prototype.insertLink = function(src, dst) { + var lbl; + lbl = src.dim.featOut + 'ch ⋅ ' + src.dim.wOut + '×' + src.dim.hOut; return this.graph.setEdge(src.name, dst.name, { - arrowhead: 'vee' + arrowhead: 'vee', + label: lbl }); }; @@ -2370,4 +2532,15620 @@ module.exports = Renderer = (function() { })(); +},{"tableify":9,"tablesorter":10}],9:[function(require,module,exports){ +"use strict"; + +module.exports = tableify; + +function tableify(obj, columns, parents) { + var buf = []; + var type = typeof obj; + var cols; + + parents = parents || []; + + if (type !== 'object' || obj == null || obj == undefined) { + } + else if (~parents.indexOf(obj)) { + return "[Circular]"; + } + else { + parents.push(obj); + } + + if (Array.isArray(obj)) { + if (typeof obj[0] === 'object') { + buf.push('','',''); + + //loop through every object and get unique keys + var keys = {}; + obj.forEach(function (o) { + if (typeof o === 'object' && !Array.isArray(o)) { + Object.keys(o).forEach(function (k) { + keys[k] = true; + }); + } + }); + + cols = Object.keys(keys); + + cols.forEach(function (key) { + buf.push('', key, ''); + }); + + buf.push('', '', ''); + + obj.forEach(function (record) { + buf.push(''); + buf.push(tableify(record, cols, parents)); + buf.push(''); + }); + + buf.push('
'); + } + else { + buf.push('',''); + cols = []; + + obj.forEach(function (val, ix) { + cols.push(ix); + buf.push('', '', tableify(val, cols, parents), '', ''); + }); + + buf.push('','
'); + } + + } + else if (obj && typeof obj === 'object' && !Array.isArray(obj) && !(obj instanceof Date)) { + if (!columns) { + buf.push(''); + + Object.keys(obj).forEach(function (key) { + buf.push('', '', key, '', '', tableify(obj[key], false, parents), '', ''); + }); + + buf.push('
'); + } + else { + columns.forEach(function (key) { + if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { + buf.push('', tableify(obj[key], false, parents), ''); + } + else { + buf.push('', tableify(obj[key], columns, parents), ''); + } + }); + } + } + else { + buf.push(obj); + } + + if (type !== 'object' || obj == null || obj == undefined) { + } + else { + parents.pop(obj); + } + + return buf.join(''); +} + +function getClass(obj) { + return ' class="' + + ((obj && obj.constructor) + ? obj.constructor.name + : typeof obj + ).toLowerCase() + + ((obj === null) + ? ' null' + : '' + ) + + '"' + ; +} + +},{}],10:[function(require,module,exports){ +/*! tablesorter (FORK) - updated 03-18-2016 (v2.25.6)*/ +/* Includes widgets ( storage,uitheme,columns,filter,stickyHeaders,resizable,saveSort ) */ +(function(factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery'], factory); + } else if (typeof module === 'object' && typeof module.exports === 'object') { + module.exports = factory(require('jquery')); + } else { + factory(jQuery); + } +}(function($) { + +/*! TableSorter (FORK) v2.25.6 *//* +* Client-side table sorting with ease! +* @requires jQuery v1.2.6+ +* +* Copyright (c) 2007 Christian Bach +* fork maintained by Rob Garrison +* +* Examples and docs at: http://tablesorter.com +* Dual licensed under the MIT and GPL licenses: +* http://www.opensource.org/licenses/mit-license.php +* http://www.gnu.org/licenses/gpl.html +* +* @type jQuery +* @name tablesorter (FORK) +* @cat Plugins/Tablesorter +* @author Christian Bach - christian.bach@polyester.se +* @contributor Rob Garrison - https://github.com/Mottie/tablesorter +*/ +/*jshint browser:true, jquery:true, unused:false, expr: true */ +;( function( $ ) { + 'use strict'; + var ts = $.tablesorter = { + + version : '2.25.6', + + parsers : [], + widgets : [], + defaults : { + + // *** appearance + theme : 'default', // adds tablesorter-{theme} to the table for styling + widthFixed : false, // adds colgroup to fix widths of columns + showProcessing : false, // show an indeterminate timer icon in the header when the table is sorted or filtered. + + headerTemplate : '{content}',// header layout template (HTML ok); {content} = innerHTML, {icon} = // class from cssIcon + onRenderTemplate : null, // function( index, template ){ return template; }, // template is a string + onRenderHeader : null, // function( index ){}, // nothing to return + + // *** functionality + cancelSelection : true, // prevent text selection in the header + tabIndex : true, // add tabindex to header for keyboard accessibility + dateFormat : 'mmddyyyy', // other options: 'ddmmyyy' or 'yyyymmdd' + sortMultiSortKey : 'shiftKey', // key used to select additional columns + sortResetKey : 'ctrlKey', // key used to remove sorting on a column + usNumberFormat : true, // false for German '1.234.567,89' or French '1 234 567,89' + delayInit : false, // if false, the parsed table contents will not update until the first sort + serverSideSorting: false, // if true, server-side sorting should be performed because client-side sorting will be disabled, but the ui and events will still be used. + resort : true, // default setting to trigger a resort after an 'update', 'addRows', 'updateCell', etc has completed + + // *** sort options + headers : {}, // set sorter, string, empty, locked order, sortInitialOrder, filter, etc. + ignoreCase : true, // ignore case while sorting + sortForce : null, // column(s) first sorted; always applied + sortList : [], // Initial sort order; applied initially; updated when manually sorted + sortAppend : null, // column(s) sorted last; always applied + sortStable : false, // when sorting two rows with exactly the same content, the original sort order is maintained + + sortInitialOrder : 'asc', // sort direction on first click + sortLocaleCompare: false, // replace equivalent character (accented characters) + sortReset : false, // third click on the header will reset column to default - unsorted + sortRestart : false, // restart sort to 'sortInitialOrder' when clicking on previously unsorted columns + + emptyTo : 'bottom', // sort empty cell to bottom, top, none, zero, emptyMax, emptyMin + stringTo : 'max', // sort strings in numerical column as max, min, top, bottom, zero + duplicateSpan : true, // colspan cells in the tbody will have duplicated content in the cache for each spanned column + textExtraction : 'basic', // text extraction method/function - function( node, table, cellIndex ){} + textAttribute : 'data-text',// data-attribute that contains alternate cell text (used in default textExtraction function) + textSorter : null, // choose overall or specific column sorter function( a, b, direction, table, columnIndex ) [alt: ts.sortText] + numberSorter : null, // choose overall numeric sorter function( a, b, direction, maxColumnValue ) + + // *** widget options + widgets: [], // method to add widgets, e.g. widgets: ['zebra'] + widgetOptions : { + zebra : [ 'even', 'odd' ] // zebra widget alternating row class names + }, + initWidgets : true, // apply widgets on tablesorter initialization + widgetClass : 'widget-{name}', // table class name template to match to include a widget + + // *** callbacks + initialized : null, // function( table ){}, + + // *** extra css class names + tableClass : '', + cssAsc : '', + cssDesc : '', + cssNone : '', + cssHeader : '', + cssHeaderRow : '', + cssProcessing : '', // processing icon applied to header during sort/filter + + cssChildRow : 'tablesorter-childRow', // class name indiciating that a row is to be attached to the its parent + cssInfoBlock : 'tablesorter-infoOnly', // don't sort tbody with this class name (only one class name allowed here!) + cssNoSort : 'tablesorter-noSort', // class name added to element inside header; clicking on it won't cause a sort + cssIgnoreRow : 'tablesorter-ignoreRow', // header row to ignore; cells within this row will not be added to c.$headers + + cssIcon : 'tablesorter-icon', // if this class does not exist, the {icon} will not be added from the headerTemplate + cssIconNone : '', // class name added to the icon when there is no column sort + cssIconAsc : '', // class name added to the icon when the column has an ascending sort + cssIconDesc : '', // class name added to the icon when the column has a descending sort + + // *** events + pointerClick : 'click', + pointerDown : 'mousedown', + pointerUp : 'mouseup', + + // *** selectors + selectorHeaders : '> thead th, > thead td', + selectorSort : 'th, td', // jQuery selector of content within selectorHeaders that is clickable to trigger a sort + selectorRemove : '.remove-me', + + // *** advanced + debug : false, + + // *** Internal variables + headerList: [], + empties: {}, + strings: {}, + parsers: [] + + // removed: widgetZebra: { css: ['even', 'odd'] } + + }, + + // internal css classes - these will ALWAYS be added to + // the table and MUST only contain one class name - fixes #381 + css : { + table : 'tablesorter', + cssHasChild: 'tablesorter-hasChildRow', + childRow : 'tablesorter-childRow', + colgroup : 'tablesorter-colgroup', + header : 'tablesorter-header', + headerRow : 'tablesorter-headerRow', + headerIn : 'tablesorter-header-inner', + icon : 'tablesorter-icon', + processing : 'tablesorter-processing', + sortAsc : 'tablesorter-headerAsc', + sortDesc : 'tablesorter-headerDesc', + sortNone : 'tablesorter-headerUnSorted' + }, + + // labels applied to sortable headers for accessibility (aria) support + language : { + sortAsc : 'Ascending sort applied, ', + sortDesc : 'Descending sort applied, ', + sortNone : 'No sort applied, ', + sortDisabled : 'sorting is disabled', + nextAsc : 'activate to apply an ascending sort', + nextDesc : 'activate to apply a descending sort', + nextNone : 'activate to remove the sort' + }, + + regex : { + templateContent : /\{content\}/g, + templateIcon : /\{icon\}/g, + templateName : /\{name\}/i, + spaces : /\s+/g, + nonWord : /\W/g, + formElements : /(input|select|button|textarea)/i, + + // *** sort functions *** + // regex used in natural sort + // chunk/tokenize numbers & letters + chunk : /(^([+\-]?(?:\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi, + // replace chunks @ ends + chunks : /(^\\0|\\0$)/, + hex : /^0x[0-9a-f]+$/i, + + // *** formatFloat *** + comma : /,/g, + digitNonUS : /[\s|\.]/g, + digitNegativeTest : /^\s*\([.\d]+\)/, + digitNegativeReplace : /^\s*\(([.\d]+)\)/, + + // *** isDigit *** + digitTest : /^[\-+(]?\d+[)]?$/, + digitReplace : /[,.'"\s]/g + + }, + + // digit sort, text location + string : { + max : 1, + min : -1, + emptymin : 1, + emptymax : -1, + zero : 0, + none : 0, + 'null' : 0, + top : true, + bottom : false + }, + + keyCodes : { + enter : 13 + }, + + // placeholder date parser data (globalize) + dates : {}, + + // These methods can be applied on table.config instance + instanceMethods : {}, + + /* + ▄█████ ██████ ██████ ██ ██ █████▄ + ▀█▄ ██▄▄ ██ ██ ██ ██▄▄██ + ▀█▄ ██▀▀ ██ ██ ██ ██▀▀▀ + █████▀ ██████ ██ ▀████▀ ██ + */ + + setup : function( table, c ) { + // if no thead or tbody, or tablesorter is already present, quit + if ( !table || !table.tHead || table.tBodies.length === 0 || table.hasInitialized === true ) { + if ( c.debug ) { + if ( table.hasInitialized ) { + console.warn( 'Stopping initialization. Tablesorter has already been initialized' ); + } else { + console.error( 'Stopping initialization! No table, thead or tbody', table ); + } + } + return; + } + + var tmp = '', + $table = $( table ), + meta = $.metadata; + // initialization flag + table.hasInitialized = false; + // table is being processed flag + table.isProcessing = true; + // make sure to store the config object + table.config = c; + // save the settings where they read + $.data( table, 'tablesorter', c ); + if ( c.debug ) { + console[ console.group ? 'group' : 'log' ]( 'Initializing tablesorter' ); + $.data( table, 'startoveralltimer', new Date() ); + } + + // removing this in version 3 (only supports jQuery 1.7+) + c.supportsDataObject = ( function( version ) { + version[ 0 ] = parseInt( version[ 0 ], 10 ); + return ( version[ 0 ] > 1 ) || ( version[ 0 ] === 1 && parseInt( version[ 1 ], 10 ) >= 4 ); + })( $.fn.jquery.split( '.' ) ); + // ensure case insensitivity + c.emptyTo = c.emptyTo.toLowerCase(); + c.stringTo = c.stringTo.toLowerCase(); + c.last = { sortList : [], clickedIndex : -1 }; + // add table theme class only if there isn't already one there + if ( !/tablesorter\-/.test( $table.attr( 'class' ) ) ) { + tmp = ( c.theme !== '' ? ' tablesorter-' + c.theme : '' ); + } + c.table = table; + c.$table = $table + .addClass( ts.css.table + ' ' + c.tableClass + tmp ) + .attr( 'role', 'grid' ); + c.$headers = $table.find( c.selectorHeaders ); + + // give the table a unique id, which will be used in namespace binding + if ( !c.namespace ) { + c.namespace = '.tablesorter' + Math.random().toString( 16 ).slice( 2 ); + } else { + // make sure namespace starts with a period & doesn't have weird characters + c.namespace = '.' + c.namespace.replace( ts.regex.nonWord, '' ); + } + + c.$table.children().children( 'tr' ).attr( 'role', 'row' ); + c.$tbodies = $table.children( 'tbody:not(.' + c.cssInfoBlock + ')' ).attr({ + 'aria-live' : 'polite', + 'aria-relevant' : 'all' + }); + if ( c.$table.children( 'caption' ).length ) { + tmp = c.$table.children( 'caption' )[ 0 ]; + if ( !tmp.id ) { tmp.id = c.namespace.slice( 1 ) + 'caption'; } + c.$table.attr( 'aria-labelledby', tmp.id ); + } + c.widgetInit = {}; // keep a list of initialized widgets + // change textExtraction via data-attribute + c.textExtraction = c.$table.attr( 'data-text-extraction' ) || c.textExtraction || 'basic'; + // build headers + ts.buildHeaders( c ); + // fixate columns if the users supplies the fixedWidth option + // do this after theme has been applied + ts.fixColumnWidth( table ); + // add widgets from class name + ts.addWidgetFromClass( table ); + // add widget options before parsing (e.g. grouping widget has parser settings) + ts.applyWidgetOptions( table ); + // try to auto detect column type, and store in tables config + ts.setupParsers( c ); + // start total row count at zero + c.totalRows = 0; + // build the cache for the tbody cells + // delayInit will delay building the cache until the user starts a sort + if ( !c.delayInit ) { ts.buildCache( c ); } + // bind all header events and methods + ts.bindEvents( table, c.$headers, true ); + ts.bindMethods( c ); + // get sort list from jQuery data or metadata + // in jQuery < 1.4, an error occurs when calling $table.data() + if ( c.supportsDataObject && typeof $table.data().sortlist !== 'undefined' ) { + c.sortList = $table.data().sortlist; + } else if ( meta && ( $table.metadata() && $table.metadata().sortlist ) ) { + c.sortList = $table.metadata().sortlist; + } + // apply widget init code + ts.applyWidget( table, true ); + // if user has supplied a sort list to constructor + if ( c.sortList.length > 0 ) { + ts.sortOn( c, c.sortList, {}, !c.initWidgets ); + } else { + ts.setHeadersCss( c ); + if ( c.initWidgets ) { + // apply widget format + ts.applyWidget( table, false ); + } + } + + // show processesing icon + if ( c.showProcessing ) { + $table + .unbind( 'sortBegin' + c.namespace + ' sortEnd' + c.namespace ) + .bind( 'sortBegin' + c.namespace + ' sortEnd' + c.namespace, function( e ) { + clearTimeout( c.timerProcessing ); + ts.isProcessing( table ); + if ( e.type === 'sortBegin' ) { + c.timerProcessing = setTimeout( function() { + ts.isProcessing( table, true ); + }, 500 ); + } + }); + } + + // initialized + table.hasInitialized = true; + table.isProcessing = false; + if ( c.debug ) { + console.log( 'Overall initialization time: ' + ts.benchmark( $.data( table, 'startoveralltimer' ) ) ); + if ( c.debug && console.groupEnd ) { console.groupEnd(); } + } + $table.triggerHandler( 'tablesorter-initialized', table ); + if ( typeof c.initialized === 'function' ) { + c.initialized( table ); + } + }, + + bindMethods : function( c ) { + var $table = c.$table, + namespace = c.namespace, + events = ( 'sortReset update updateRows updateAll updateHeaders addRows updateCell updateComplete ' + + 'sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup ' + + 'mouseleave ' ).split( ' ' ) + .join( namespace + ' ' ); + // apply easy methods that trigger bound events + $table + .unbind( events.replace( ts.regex.spaces, ' ' ) ) + .bind( 'sortReset' + namespace, function( e, callback ) { + e.stopPropagation(); + // using this.config to ensure functions are getting a non-cached version of the config + ts.sortReset( this.config, callback ); + }) + .bind( 'updateAll' + namespace, function( e, resort, callback ) { + e.stopPropagation(); + ts.updateAll( this.config, resort, callback ); + }) + .bind( 'update' + namespace + ' updateRows' + namespace, function( e, resort, callback ) { + e.stopPropagation(); + ts.update( this.config, resort, callback ); + }) + .bind( 'updateHeaders' + namespace, function( e, callback ) { + e.stopPropagation(); + ts.updateHeaders( this.config, callback ); + }) + .bind( 'updateCell' + namespace, function( e, cell, resort, callback ) { + e.stopPropagation(); + ts.updateCell( this.config, cell, resort, callback ); + }) + .bind( 'addRows' + namespace, function( e, $row, resort, callback ) { + e.stopPropagation(); + ts.addRows( this.config, $row, resort, callback ); + }) + .bind( 'updateComplete' + namespace, function() { + this.isUpdating = false; + }) + .bind( 'sorton' + namespace, function( e, list, callback, init ) { + e.stopPropagation(); + ts.sortOn( this.config, list, callback, init ); + }) + .bind( 'appendCache' + namespace, function( e, callback, init ) { + e.stopPropagation(); + ts.appendCache( this.config, init ); + if ( $.isFunction( callback ) ) { + callback( this ); + } + }) + // $tbodies variable is used by the tbody sorting widget + .bind( 'updateCache' + namespace, function( e, callback, $tbodies ) { + e.stopPropagation(); + ts.updateCache( this.config, callback, $tbodies ); + }) + .bind( 'applyWidgetId' + namespace, function( e, id ) { + e.stopPropagation(); + ts.applyWidgetId( this, id ); + }) + .bind( 'applyWidgets' + namespace, function( e, init ) { + e.stopPropagation(); + // apply widgets + ts.applyWidget( this, init ); + }) + .bind( 'refreshWidgets' + namespace, function( e, all, dontapply ) { + e.stopPropagation(); + ts.refreshWidgets( this, all, dontapply ); + }) + .bind( 'removeWidget' + namespace, function( e, name, refreshing ) { + e.stopPropagation(); + ts.removeWidget( this, name, refreshing ); + }) + .bind( 'destroy' + namespace, function( e, removeClasses, callback ) { + e.stopPropagation(); + ts.destroy( this, removeClasses, callback ); + }) + .bind( 'resetToLoadState' + namespace, function( e ) { + e.stopPropagation(); + // remove all widgets + ts.removeWidget( this, true, false ); + // restore original settings; this clears out current settings, but does not clear + // values saved to storage. + c = $.extend( true, ts.defaults, c.originalSettings ); + this.hasInitialized = false; + // setup the entire table again + ts.setup( this, c ); + }); + }, + + bindEvents : function( table, $headers, core ) { + table = $( table )[ 0 ]; + var tmp, + c = table.config, + namespace = c.namespace, + downTarget = null; + if ( core !== true ) { + $headers.addClass( namespace.slice( 1 ) + '_extra_headers' ); + tmp = $.fn.closest ? $headers.closest( 'table' )[ 0 ] : $headers.parents( 'table' )[ 0 ]; + if ( tmp && tmp.nodeName === 'TABLE' && tmp !== table ) { + $( tmp ).addClass( namespace.slice( 1 ) + '_extra_table' ); + } + } + tmp = ( c.pointerDown + ' ' + c.pointerUp + ' ' + c.pointerClick + ' sort keyup ' ) + .replace( ts.regex.spaces, ' ' ) + .split( ' ' ) + .join( namespace + ' ' ); + // apply event handling to headers and/or additional headers (stickyheaders, scroller, etc) + $headers + // http://stackoverflow.com/questions/5312849/jquery-find-self; + .find( c.selectorSort ) + .add( $headers.filter( c.selectorSort ) ) + .unbind( tmp ) + .bind( tmp, function( e, external ) { + var $cell, cell, temp, + $target = $( e.target ), + // wrap event type in spaces, so the match doesn't trigger on inner words + type = ' ' + e.type + ' '; + // only recognize left clicks + if ( ( ( e.which || e.button ) !== 1 && !type.match( ' ' + c.pointerClick + ' | sort | keyup ' ) ) || + // allow pressing enter + ( type === ' keyup ' && e.which !== ts.keyCodes.enter ) || + // allow triggering a click event (e.which is undefined) & ignore physical clicks + ( type.match( ' ' + c.pointerClick + ' ' ) && typeof e.which !== 'undefined' ) ) { + return; + } + // ignore mouseup if mousedown wasn't on the same target + if ( type.match( ' ' + c.pointerUp + ' ' ) && downTarget !== e.target && external !== true ) { + return; + } + // set target on mousedown + if ( type.match( ' ' + c.pointerDown + ' ' ) ) { + downTarget = e.target; + // preventDefault needed or jQuery v1.3.2 and older throws an + // "Uncaught TypeError: handler.apply is not a function" error + temp = $target.jquery.split( '.' ); + if ( temp[ 0 ] === '1' && temp[ 1 ] < 4 ) { e.preventDefault(); } + return; + } + downTarget = null; + // prevent sort being triggered on form elements + if ( ts.regex.formElements.test( e.target.nodeName ) || + // nosort class name, or elements within a nosort container + $target.hasClass( c.cssNoSort ) || $target.parents( '.' + c.cssNoSort ).length > 0 || + // elements within a button + $target.parents( 'button' ).length > 0 ) { + return !c.cancelSelection; + } + if ( c.delayInit && ts.isEmptyObject( c.cache ) ) { + ts.buildCache( c ); + } + // jQuery v1.2.6 doesn't have closest() + $cell = $.fn.closest ? $( this ).closest( 'th, td' ) : + /TH|TD/.test( this.nodeName ) ? $( this ) : $( this ).parents( 'th, td' ); + // reference original table headers and find the same cell + // don't use $headers or IE8 throws an error - see #987 + temp = $headers.index( $cell ); + c.last.clickedIndex = ( temp < 0 ) ? $cell.attr( 'data-column' ) : temp; + // use column index if $headers is undefined + cell = c.$headers[ c.last.clickedIndex ]; + if ( cell && !cell.sortDisabled ) { + ts.initSort( c, cell, e ); + } + }); + if ( c.cancelSelection ) { + // cancel selection + $headers + .attr( 'unselectable', 'on' ) + .bind( 'selectstart', false ) + .css({ + 'user-select' : 'none', + 'MozUserSelect' : 'none' // not needed for jQuery 1.8+ + }); + } + }, + + buildHeaders : function( c ) { + var $temp, icon, timer, indx; + c.headerList = []; + c.headerContent = []; + c.sortVars = []; + if ( c.debug ) { + timer = new Date(); + } + // children tr in tfoot - see issue #196 & #547 + // don't pass table.config to computeColumnIndex here - widgets (math) pass it to "quickly" index tbody cells + c.columns = ts.computeColumnIndex( c.$table.children( 'thead, tfoot' ).children( 'tr' ) ); + // add icon if cssIcon option exists + icon = c.cssIcon ? + '' : + ''; + // redefine c.$headers here in case of an updateAll that replaces or adds an entire header cell - see #683 + c.$headers = $( $.map( c.$table.find( c.selectorHeaders ), function( elem, index ) { + var configHeaders, header, column, template, tmp, + $elem = $( elem ); + // ignore cell (don't add it to c.$headers) if row has ignoreRow class + if ( $elem.parent().hasClass( c.cssIgnoreRow ) ) { return; } + // make sure to get header cell & not column indexed cell + configHeaders = ts.getColumnData( c.table, c.headers, index, true ); + // save original header content + c.headerContent[ index ] = $elem.html(); + // if headerTemplate is empty, don't reformat the header cell + if ( c.headerTemplate !== '' && !$elem.find( '.' + ts.css.headerIn ).length ) { + // set up header template + template = c.headerTemplate + .replace( ts.regex.templateContent, $elem.html() ) + .replace( ts.regex.templateIcon, $elem.find( '.' + ts.css.icon ).length ? '' : icon ); + if ( c.onRenderTemplate ) { + header = c.onRenderTemplate.apply( $elem, [ index, template ] ); + // only change t if something is returned + if ( header && typeof header === 'string' ) { + template = header; + } + } + $elem.html( '
' + template + '
' ); // faster than wrapInner + } + if ( c.onRenderHeader ) { + c.onRenderHeader.apply( $elem, [ index, c, c.$table ] ); + } + column = parseInt( $elem.attr( 'data-column' ), 10 ); + elem.column = column; + tmp = ts.getData( $elem, configHeaders, 'sortInitialOrder' ) || c.sortInitialOrder; + // this may get updated numerous times if there are multiple rows + c.sortVars[ column ] = { + count : -1, // set to -1 because clicking on the header automatically adds one + order: ts.getOrder( tmp ) ? + [ 1, 0, 2 ] : // desc, asc, unsorted + [ 0, 1, 2 ], // asc, desc, unsorted + lockedOrder : false + }; + tmp = ts.getData( $elem, configHeaders, 'lockedOrder' ) || false; + if ( typeof tmp !== 'undefined' && tmp !== false ) { + c.sortVars[ column ].lockedOrder = true; + c.sortVars[ column ].order = ts.getOrder( tmp ) ? [ 1, 1, 1 ] : [ 0, 0, 0 ]; + } + // add cell to headerList + c.headerList[ index ] = elem; + // add to parent in case there are multiple rows + $elem + .addClass( ts.css.header + ' ' + c.cssHeader ) + .parent() + .addClass( ts.css.headerRow + ' ' + c.cssHeaderRow ) + .attr( 'role', 'row' ); + // allow keyboard cursor to focus on element + if ( c.tabIndex ) { + $elem.attr( 'tabindex', 0 ); + } + return elem; + }) ); + // cache headers per column + c.$headerIndexed = []; + for ( indx = 0; indx < c.columns; indx++ ) { + // colspan in header making a column undefined + if ( ts.isEmptyObject( c.sortVars[ indx ] ) ) { + c.sortVars[ indx ] = {}; + } + $temp = c.$headers.filter( '[data-column="' + indx + '"]' ); + // target sortable column cells, unless there are none, then use non-sortable cells + // .last() added in jQuery 1.4; use .filter(':last') to maintain compatibility with jQuery v1.2.6 + c.$headerIndexed[ indx ] = $temp.length ? + $temp.not( '.sorter-false' ).length ? + $temp.not( '.sorter-false' ).filter( ':last' ) : + $temp.filter( ':last' ) : + $(); + } + c.$table.find( c.selectorHeaders ).attr({ + scope: 'col', + role : 'columnheader' + }); + // enable/disable sorting + ts.updateHeader( c ); + if ( c.debug ) { + console.log( 'Built headers:' + ts.benchmark( timer ) ); + console.log( c.$headers ); + } + }, + + // Use it to add a set of methods to table.config which will be available for all tables. + // This should be done before table initialization + addInstanceMethods : function( methods ) { + $.extend( ts.instanceMethods, methods ); + }, + + /* + █████▄ ▄████▄ █████▄ ▄█████ ██████ █████▄ ▄█████ + ██▄▄██ ██▄▄██ ██▄▄██ ▀█▄ ██▄▄ ██▄▄██ ▀█▄ + ██▀▀▀ ██▀▀██ ██▀██ ▀█▄ ██▀▀ ██▀██ ▀█▄ + ██ ██ ██ ██ ██ █████▀ ██████ ██ ██ █████▀ + */ + setupParsers : function( c, $tbodies ) { + var rows, list, span, max, colIndex, indx, header, configHeaders, + noParser, parser, extractor, time, tbody, len, + table = c.table, + tbodyIndex = 0, + debug = {}; + // update table bodies in case we start with an empty table + c.$tbodies = c.$table.children( 'tbody:not(.' + c.cssInfoBlock + ')' ); + tbody = typeof $tbodies === 'undefined' ? c.$tbodies : $tbodies; + len = tbody.length; + if ( len === 0 ) { + return c.debug ? console.warn( 'Warning: *Empty table!* Not building a parser cache' ) : ''; + } else if ( c.debug ) { + time = new Date(); + console[ console.group ? 'group' : 'log' ]( 'Detecting parsers for each column' ); + } + list = { + extractors: [], + parsers: [] + }; + while ( tbodyIndex < len ) { + rows = tbody[ tbodyIndex ].rows; + if ( rows.length ) { + colIndex = 0; + max = c.columns; + for ( indx = 0; indx < max; indx++ ) { + header = c.$headerIndexed[ colIndex ]; + if ( header && header.length ) { + // get column indexed table cell + configHeaders = ts.getColumnData( table, c.headers, colIndex ); + // get column parser/extractor + extractor = ts.getParserById( ts.getData( header, configHeaders, 'extractor' ) ); + parser = ts.getParserById( ts.getData( header, configHeaders, 'sorter' ) ); + noParser = ts.getData( header, configHeaders, 'parser' ) === 'false'; + // empty cells behaviour - keeping emptyToBottom for backwards compatibility + c.empties[colIndex] = ( + ts.getData( header, configHeaders, 'empty' ) || + c.emptyTo || ( c.emptyToBottom ? 'bottom' : 'top' ) ).toLowerCase(); + // text strings behaviour in numerical sorts + c.strings[colIndex] = ( + ts.getData( header, configHeaders, 'string' ) || + c.stringTo || + 'max' ).toLowerCase(); + if ( noParser ) { + parser = ts.getParserById( 'no-parser' ); + } + if ( !extractor ) { + // For now, maybe detect someday + extractor = false; + } + if ( !parser ) { + parser = ts.detectParserForColumn( c, rows, -1, colIndex ); + } + if ( c.debug ) { + debug[ '(' + colIndex + ') ' + header.text() ] = { + parser : parser.id, + extractor : extractor ? extractor.id : 'none', + string : c.strings[ colIndex ], + empty : c.empties[ colIndex ] + }; + } + list.parsers[ colIndex ] = parser; + list.extractors[ colIndex ] = extractor; + span = header[ 0 ].colSpan - 1; + if ( span > 0 ) { + colIndex += span; + max += span; + while ( span + 1 > 0 ) { + // set colspan columns to use the same parsers & extractors + list.parsers[ colIndex - span ] = parser; + list.extractors[ colIndex - span ] = extractor; + span--; + } + } + } + colIndex++; + } + } + tbodyIndex += ( list.parsers.length ) ? len : 1; + } + if ( c.debug ) { + if ( !ts.isEmptyObject( debug ) ) { + console[ console.table ? 'table' : 'log' ]( debug ); + } else { + console.warn( ' No parsers detected!' ); + } + console.log( 'Completed detecting parsers' + ts.benchmark( time ) ); + if ( console.groupEnd ) { console.groupEnd(); } + } + c.parsers = list.parsers; + c.extractors = list.extractors; + }, + + addParser : function( parser ) { + var indx, + len = ts.parsers.length, + add = true; + for ( indx = 0; indx < len; indx++ ) { + if ( ts.parsers[ indx ].id.toLowerCase() === parser.id.toLowerCase() ) { + add = false; + } + } + if ( add ) { + ts.parsers[ ts.parsers.length ] = parser; + } + }, + + getParserById : function( name ) { + /*jshint eqeqeq:false */ + if ( name == 'false' ) { return false; } + var indx, + len = ts.parsers.length; + for ( indx = 0; indx < len; indx++ ) { + if ( ts.parsers[ indx ].id.toLowerCase() === ( name.toString() ).toLowerCase() ) { + return ts.parsers[ indx ]; + } + } + return false; + }, + + detectParserForColumn : function( c, rows, rowIndex, cellIndex ) { + var cur, $node, row, + indx = ts.parsers.length, + node = false, + nodeValue = '', + keepLooking = true; + while ( nodeValue === '' && keepLooking ) { + rowIndex++; + row = rows[ rowIndex ]; + // stop looking after 50 empty rows + if ( row && rowIndex < 50 ) { + if ( row.className.indexOf( ts.cssIgnoreRow ) < 0 ) { + node = rows[ rowIndex ].cells[ cellIndex ]; + nodeValue = ts.getElementText( c, node, cellIndex ); + $node = $( node ); + if ( c.debug ) { + console.log( 'Checking if value was empty on row ' + rowIndex + ', column: ' + + cellIndex + ': "' + nodeValue + '"' ); + } + } + } else { + keepLooking = false; + } + } + while ( --indx >= 0 ) { + cur = ts.parsers[ indx ]; + // ignore the default text parser because it will always be true + if ( cur && cur.id !== 'text' && cur.is && cur.is( nodeValue, c.table, node, $node ) ) { + return cur; + } + } + // nothing found, return the generic parser (text) + return ts.getParserById( 'text' ); + }, + + getElementText : function( c, node, cellIndex ) { + if ( !node ) { return ''; } + var tmp, + extract = c.textExtraction || '', + // node could be a jquery object + // http://jsperf.com/jquery-vs-instanceof-jquery/2 + $node = node.jquery ? node : $( node ); + if ( typeof extract === 'string' ) { + // check data-attribute first when set to 'basic'; don't use node.innerText - it's really slow! + // http://www.kellegous.com/j/2013/02/27/innertext-vs-textcontent/ + if ( extract === 'basic' && typeof ( tmp = $node.attr( c.textAttribute ) ) !== 'undefined' ) { + return $.trim( tmp ); + } + return $.trim( node.textContent || $node.text() ); + } else { + if ( typeof extract === 'function' ) { + return $.trim( extract( $node[ 0 ], c.table, cellIndex ) ); + } else if ( typeof ( tmp = ts.getColumnData( c.table, extract, cellIndex ) ) === 'function' ) { + return $.trim( tmp( $node[ 0 ], c.table, cellIndex ) ); + } + } + // fallback + return $.trim( $node[ 0 ].textContent || $node.text() ); + }, + + // centralized function to extract/parse cell contents + getParsedText : function( c, cell, colIndex, txt ) { + if ( typeof txt === 'undefined' ) { + txt = ts.getElementText( c, cell, colIndex ); + } + // if no parser, make sure to return the txt + var val = '' + txt, + parser = c.parsers[ colIndex ], + extractor = c.extractors[ colIndex ]; + if ( parser ) { + // do extract before parsing, if there is one + if ( extractor && typeof extractor.format === 'function' ) { + txt = extractor.format( txt, c.table, cell, colIndex ); + } + // allow parsing if the string is empty, previously parsing would change it to zero, + // in case the parser needs to extract data from the table cell attributes + val = parser.id === 'no-parser' ? '' : + // make sure txt is a string (extractor may have converted it) + parser.format( '' + txt, c.table, cell, colIndex ); + if ( c.ignoreCase && typeof val === 'string' ) { + val = val.toLowerCase(); + } + } + return val; + }, + + /* + ▄████▄ ▄████▄ ▄████▄ ██ ██ ██████ + ██ ▀▀ ██▄▄██ ██ ▀▀ ██▄▄██ ██▄▄ + ██ ▄▄ ██▀▀██ ██ ▄▄ ██▀▀██ ██▀▀ + ▀████▀ ██ ██ ▀████▀ ██ ██ ██████ + */ + buildCache : function( c, callback, $tbodies ) { + var cache, val, txt, rowIndex, colIndex, tbodyIndex, $tbody, $row, + cols, $cells, cell, cacheTime, totalRows, rowData, prevRowData, + colMax, span, cacheIndex, hasParser, max, len, index, + table = c.table, + parsers = c.parsers; + // update tbody variable + c.$tbodies = c.$table.children( 'tbody:not(.' + c.cssInfoBlock + ')' ); + $tbody = typeof $tbodies === 'undefined' ? c.$tbodies : $tbodies, + c.cache = {}; + c.totalRows = 0; + // if no parsers found, return - it's an empty table. + if ( !parsers ) { + return c.debug ? console.warn( 'Warning: *Empty table!* Not building a cache' ) : ''; + } + if ( c.debug ) { + cacheTime = new Date(); + } + // processing icon + if ( c.showProcessing ) { + ts.isProcessing( table, true ); + } + for ( tbodyIndex = 0; tbodyIndex < $tbody.length; tbodyIndex++ ) { + colMax = []; // column max value per tbody + cache = c.cache[ tbodyIndex ] = { + normalized: [] // array of normalized row data; last entry contains 'rowData' above + // colMax: # // added at the end + }; + + totalRows = ( $tbody[ tbodyIndex ] && $tbody[ tbodyIndex ].rows.length ) || 0; + for ( rowIndex = 0; rowIndex < totalRows; ++rowIndex ) { + rowData = { + // order: original row order # + // $row : jQuery Object[] + child: [], // child row text (filter widget) + raw: [] // original row text + }; + /** Add the table data to main data array */ + $row = $( $tbody[ tbodyIndex ].rows[ rowIndex ] ); + cols = []; + // if this is a child row, add it to the last row's children and continue to the next row + // ignore child row class, if it is the first row + if ( $row.hasClass( c.cssChildRow ) && rowIndex !== 0 ) { + len = cache.normalized.length - 1; + prevRowData = cache.normalized[ len ][ c.columns ]; + prevRowData.$row = prevRowData.$row.add( $row ); + // add 'hasChild' class name to parent row + if ( !$row.prev().hasClass( c.cssChildRow ) ) { + $row.prev().addClass( ts.css.cssHasChild ); + } + // save child row content (un-parsed!) + $cells = $row.children( 'th, td' ); + len = prevRowData.child.length; + prevRowData.child[ len ] = []; + // child row content does not account for colspans/rowspans; so indexing may be off + cacheIndex = 0; + max = c.columns; + for ( colIndex = 0; colIndex < max; colIndex++ ) { + cell = $cells[ colIndex ]; + if ( cell ) { + prevRowData.child[ len ][ colIndex ] = ts.getParsedText( c, cell, colIndex ); + span = $cells[ colIndex ].colSpan - 1; + if ( span > 0 ) { + cacheIndex += span; + max += span; + } + } + cacheIndex++; + } + // go to the next for loop + continue; + } + rowData.$row = $row; + rowData.order = rowIndex; // add original row position to rowCache + cacheIndex = 0; + max = c.columns; + for ( colIndex = 0; colIndex < max; ++colIndex ) { + cell = $row[ 0 ].cells[ colIndex ]; + if ( cell && cacheIndex < c.columns ) { + hasParser = typeof parsers[ cacheIndex ] !== 'undefined'; + if ( !hasParser && c.debug ) { + console.warn( 'No parser found for row: ' + rowIndex + ', column: ' + colIndex + + '; cell containing: "' + $(cell).text() + '"; does it have a header?' ); + } + val = ts.getElementText( c, cell, cacheIndex ); + rowData.raw[ cacheIndex ] = val; // save original row text + // save raw column text even if there is no parser set + txt = ts.getParsedText( c, cell, cacheIndex, val ); + cols[ cacheIndex ] = txt; + if ( hasParser && ( parsers[ cacheIndex ].type || '' ).toLowerCase() === 'numeric' ) { + // determine column max value (ignore sign) + colMax[ cacheIndex ] = Math.max( Math.abs( txt ) || 0, colMax[ cacheIndex ] || 0 ); + } + // allow colSpan in tbody + span = cell.colSpan - 1; + if ( span > 0 ) { + index = 0; + while ( index <= span ) { + // duplicate text (or not) to spanned columns + rowData.raw[ cacheIndex + index ] = c.duplicateSpan || index === 0 ? val : ''; + cols[ cacheIndex + index ] = c.duplicateSpan || index === 0 ? val : ''; + index++; + } + cacheIndex += span; + max += span; + } + } + cacheIndex++; + } + // ensure rowData is always in the same location (after the last column) + cols[ c.columns ] = rowData; + cache.normalized[ cache.normalized.length ] = cols; + } + cache.colMax = colMax; + // total up rows, not including child rows + c.totalRows += cache.normalized.length; + + } + if ( c.showProcessing ) { + ts.isProcessing( table ); // remove processing icon + } + if ( c.debug ) { + len = Math.min( 5, c.cache[ 0 ].normalized.length ); + console[ console.group ? 'group' : 'log' ]( 'Building cache for ' + c.totalRows + + ' rows (showing ' + len + ' rows in log)' + ts.benchmark( cacheTime ) ); + val = {}; + for ( colIndex = 0; colIndex < c.columns; colIndex++ ) { + for ( cacheIndex = 0; cacheIndex < len; cacheIndex++ ) { + if ( !val[ 'row: ' + cacheIndex ] ) { + val[ 'row: ' + cacheIndex ] = {}; + } + val[ 'row: ' + cacheIndex ][ c.$headerIndexed[ colIndex ].text() ] = + c.cache[ 0 ].normalized[ cacheIndex ][ colIndex ]; + } + } + console[ console.table ? 'table' : 'log' ]( val ); + if ( console.groupEnd ) { console.groupEnd(); } + } + if ( $.isFunction( callback ) ) { + callback( table ); + } + }, + + getColumnText : function( table, column, callback, rowFilter ) { + table = $( table )[0]; + var tbodyIndex, rowIndex, cache, row, tbodyLen, rowLen, raw, parsed, $cell, result, + hasCallback = typeof callback === 'function', + allColumns = column === 'all', + data = { raw : [], parsed: [], $cell: [] }, + c = table.config; + if ( ts.isEmptyObject( c ) ) { + if ( c.debug ) { + console.warn( 'No cache found - aborting getColumnText function!' ); + } + } else { + tbodyLen = c.$tbodies.length; + for ( tbodyIndex = 0; tbodyIndex < tbodyLen; tbodyIndex++ ) { + cache = c.cache[ tbodyIndex ].normalized; + rowLen = cache.length; + for ( rowIndex = 0; rowIndex < rowLen; rowIndex++ ) { + row = cache[ rowIndex ]; + if ( rowFilter && !row[ c.columns ].$row.is( rowFilter ) ) { + continue; + } + result = true; + parsed = ( allColumns ) ? row.slice( 0, c.columns ) : row[ column ]; + row = row[ c.columns ]; + raw = ( allColumns ) ? row.raw : row.raw[ column ]; + $cell = ( allColumns ) ? row.$row.children() : row.$row.children().eq( column ); + if ( hasCallback ) { + result = callback({ + tbodyIndex : tbodyIndex, + rowIndex : rowIndex, + parsed : parsed, + raw : raw, + $row : row.$row, + $cell : $cell + }); + } + if ( result !== false ) { + data.parsed[ data.parsed.length ] = parsed; + data.raw[ data.raw.length ] = raw; + data.$cell[ data.$cell.length ] = $cell; + } + } + } + // return everything + return data; + } + }, + + /* + ██ ██ █████▄ █████▄ ▄████▄ ██████ ██████ + ██ ██ ██▄▄██ ██ ██ ██▄▄██ ██ ██▄▄ + ██ ██ ██▀▀▀ ██ ██ ██▀▀██ ██ ██▀▀ + ▀████▀ ██ █████▀ ██ ██ ██ ██████ + */ + setHeadersCss : function( c ) { + var $sorted, indx, column, + list = c.sortList, + len = list.length, + none = ts.css.sortNone + ' ' + c.cssNone, + css = [ ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc ], + cssIcon = [ c.cssIconAsc, c.cssIconDesc, c.cssIconNone ], + aria = [ 'ascending', 'descending' ], + // find the footer + $headers = c.$table + .find( 'tfoot tr' ) + .children( 'td, th' ) + .add( $( c.namespace + '_extra_headers' ) ) + .removeClass( css.join( ' ' ) ); + // remove all header information + c.$headers + .removeClass( css.join( ' ' ) ) + .addClass( none ) + .attr( 'aria-sort', 'none' ) + .find( '.' + ts.css.icon ) + .removeClass( cssIcon.join( ' ' ) ) + .addClass( cssIcon[ 2 ] ); + for ( indx = 0; indx < len; indx++ ) { + // direction = 2 means reset! + if ( list[ indx ][ 1 ] !== 2 ) { + // multicolumn sorting updating - see #1005 + // .not(function(){}) needs jQuery 1.4 + // filter(function(i, el){}) <- el is undefined in jQuery v1.2.6 + $sorted = c.$headers.filter( function( i ) { + // only include headers that are in the sortList (this includes colspans) + var include = true, + $el = c.$headers.eq( i ), + col = parseInt( $el.attr( 'data-column' ), 10 ), + end = col + c.$headers[ i ].colSpan; + for ( ; col < end; col++ ) { + include = include ? include || ts.isValueInArray( col, c.sortList ) > -1 : false; + } + return include; + }); + + // choose the :last in case there are nested columns + $sorted = $sorted + .not( '.sorter-false' ) + .filter( '[data-column="' + list[ indx ][ 0 ] + '"]' + ( len === 1 ? ':last' : '' ) ); + if ( $sorted.length ) { + for ( column = 0; column < $sorted.length; column++ ) { + if ( !$sorted[ column ].sortDisabled ) { + $sorted + .eq( column ) + .removeClass( none ) + .addClass( css[ list[ indx ][ 1 ] ] ) + .attr( 'aria-sort', aria[ list[ indx ][ 1 ] ] ) + .find( '.' + ts.css.icon ) + .removeClass( cssIcon[ 2 ] ) + .addClass( cssIcon[ list[ indx ][ 1 ] ] ); + } + } + // add sorted class to footer & extra headers, if they exist + if ( $headers.length ) { + $headers + .filter( '[data-column="' + list[ indx ][ 0 ] + '"]' ) + .removeClass( none ) + .addClass( css[ list[ indx ][ 1 ] ] ); + } + } + } + } + // add verbose aria labels + len = c.$headers.length; + for ( indx = 0; indx < len; indx++ ) { + ts.setColumnAriaLabel( c, c.$headers.eq( indx ) ); + } + }, + + // nextSort (optional), lets you disable next sort text + setColumnAriaLabel : function( c, $header, nextSort ) { + if ( $header.length ) { + var column = parseInt( $header.attr( 'data-column' ), 10 ), + tmp = $header.hasClass( ts.css.sortAsc ) ? + 'sortAsc' : + $header.hasClass( ts.css.sortDesc ) ? 'sortDesc' : 'sortNone', + txt = $.trim( $header.text() ) + ': ' + ts.language[ tmp ]; + if ( $header.hasClass( 'sorter-false' ) || nextSort === false ) { + txt += ts.language.sortDisabled; + } else { + nextSort = c.sortVars[ column ].order[ ( c.sortVars[ column ].count + 1 ) % ( c.sortReset ? 3 : 2 ) ]; + // if nextSort + txt += ts.language[ nextSort === 0 ? 'nextAsc' : nextSort === 1 ? 'nextDesc' : 'nextNone' ]; + } + $header.attr( 'aria-label', txt ); + } + }, + + updateHeader : function( c ) { + var index, isDisabled, $header, col, + table = c.table, + len = c.$headers.length; + for ( index = 0; index < len; index++ ) { + $header = c.$headers.eq( index ); + col = ts.getColumnData( table, c.headers, index, true ); + // add 'sorter-false' class if 'parser-false' is set + isDisabled = ts.getData( $header, col, 'sorter' ) === 'false' || ts.getData( $header, col, 'parser' ) === 'false'; + ts.setColumnSort( c, $header, isDisabled ); + } + }, + + setColumnSort : function( c, $header, isDisabled ) { + var id = c.table.id; + $header[ 0 ].sortDisabled = isDisabled; + $header[ isDisabled ? 'addClass' : 'removeClass' ]( 'sorter-false' ) + .attr( 'aria-disabled', '' + isDisabled ); + // disable tab index on disabled cells + if ( c.tabIndex ) { + if ( isDisabled ) { + $header.removeAttr( 'tabindex' ); + } else { + $header.attr( 'tabindex', '0' ); + } + } + // aria-controls - requires table ID + if ( id ) { + if ( isDisabled ) { + $header.removeAttr( 'aria-controls' ); + } else { + $header.attr( 'aria-controls', id ); + } + } + }, + + updateHeaderSortCount : function( c, list ) { + var col, dir, group, indx, primary, temp, val, order, + sortList = list || c.sortList, + len = sortList.length; + c.sortList = []; + for ( indx = 0; indx < len; indx++ ) { + val = sortList[ indx ]; + // ensure all sortList values are numeric - fixes #127 + col = parseInt( val[ 0 ], 10 ); + // prevents error if sorton array is wrong + if ( col < c.columns ) { + + // set order if not already defined - due to colspan header without associated header cell + // adding this check prevents a javascript error + if ( !c.sortVars[ col ].order ) { + order = c.sortVars[ col ].order = ts.getOrder( c.sortInitialOrder ) ? [ 1, 0, 2 ] : [ 0, 1, 2 ]; + c.sortVars[ col ].count = 0; + } + + order = c.sortVars[ col ].order; + dir = ( '' + val[ 1 ] ).match( /^(1|d|s|o|n)/ ); + dir = dir ? dir[ 0 ] : ''; + // 0/(a)sc (default), 1/(d)esc, (s)ame, (o)pposite, (n)ext + switch ( dir ) { + case '1' : case 'd' : // descending + dir = 1; + break; + case 's' : // same direction (as primary column) + // if primary sort is set to 's', make it ascending + dir = primary || 0; + break; + case 'o' : + temp = order[ ( primary || 0 ) % ( c.sortReset ? 3 : 2 ) ]; + // opposite of primary column; but resets if primary resets + dir = temp === 0 ? 1 : temp === 1 ? 0 : 2; + break; + case 'n' : + dir = order[ ( ++c.sortVars[ col ].count ) % ( c.sortReset ? 3 : 2 ) ]; + break; + default : // ascending + dir = 0; + break; + } + primary = indx === 0 ? dir : primary; + group = [ col, parseInt( dir, 10 ) || 0 ]; + c.sortList[ c.sortList.length ] = group; + dir = $.inArray( group[ 1 ], order ); // fixes issue #167 + c.sortVars[ col ].count = dir >= 0 ? dir : group[ 1 ] % ( c.sortReset ? 3 : 2 ); + } + } + }, + + updateAll : function( c, resort, callback ) { + var table = c.table; + table.isUpdating = true; + ts.refreshWidgets( table, true, true ); + ts.buildHeaders( c ); + ts.bindEvents( table, c.$headers, true ); + ts.bindMethods( c ); + ts.commonUpdate( c, resort, callback ); + }, + + update : function( c, resort, callback ) { + var table = c.table; + table.isUpdating = true; + // update sorting (if enabled/disabled) + ts.updateHeader( c ); + ts.commonUpdate( c, resort, callback ); + }, + + // simple header update - see #989 + updateHeaders : function( c, callback ) { + c.table.isUpdating = true; + ts.buildHeaders( c ); + ts.bindEvents( c.table, c.$headers, true ); + ts.resortComplete( c, callback ); + }, + + updateCell : function( c, cell, resort, callback ) { + if ( ts.isEmptyObject( c.cache ) ) { + // empty table, do an update instead - fixes #1099 + ts.updateHeader( c ); + ts.commonUpdate( c, resort, callback ); + return; + } + c.table.isUpdating = true; + c.$table.find( c.selectorRemove ).remove(); + // get position from the dom + var tmp, indx, row, icell, cache, len, + $tbodies = c.$tbodies, + $cell = $( cell ), + // update cache - format: function( s, table, cell, cellIndex ) + // no closest in jQuery v1.2.6 + tbodyIndex = $tbodies + .index( $.fn.closest ? $cell.closest( 'tbody' ) : $cell.parents( 'tbody' ).filter( ':first' ) ), + tbcache = c.cache[ tbodyIndex ], + $row = $.fn.closest ? $cell.closest( 'tr' ) : $cell.parents( 'tr' ).filter( ':first' ); + cell = $cell[ 0 ]; // in case cell is a jQuery object + // tbody may not exist if update is initialized while tbody is removed for processing + if ( $tbodies.length && tbodyIndex >= 0 ) { + row = $tbodies.eq( tbodyIndex ).find( 'tr' ).index( $row ); + cache = tbcache.normalized[ row ]; + len = $row[ 0 ].cells.length; + if ( len !== c.columns ) { + // colspan in here somewhere! + icell = 0; + tmp = false; + for ( indx = 0; indx < len; indx++ ) { + if ( !tmp && $row[ 0 ].cells[ indx ] !== cell ) { + icell += $row[ 0 ].cells[ indx ].colSpan; + } else { + tmp = true; + } + } + } else { + icell = $cell.index(); + } + tmp = ts.getElementText( c, cell, icell ); // raw + cache[ c.columns ].raw[ icell ] = tmp; + tmp = ts.getParsedText( c, cell, icell, tmp ); + cache[ icell ] = tmp; // parsed + cache[ c.columns ].$row = $row; + if ( ( c.parsers[ icell ].type || '' ).toLowerCase() === 'numeric' ) { + // update column max value (ignore sign) + tbcache.colMax[ icell ] = Math.max( Math.abs( tmp ) || 0, tbcache.colMax[ icell ] || 0 ); + } + tmp = resort !== 'undefined' ? resort : c.resort; + if ( tmp !== false ) { + // widgets will be reapplied + ts.checkResort( c, tmp, callback ); + } else { + // don't reapply widgets is resort is false, just in case it causes + // problems with element focus + ts.resortComplete( c, callback ); + } + } else { + if ( c.debug ) { + console.error( 'updateCell aborted, tbody missing or not within the indicated table' ); + } + c.table.isUpdating = false; + } + }, + + addRows : function( c, $row, resort, callback ) { + var txt, val, tbodyIndex, rowIndex, rows, cellIndex, len, order, + cacheIndex, rowData, cells, cell, span, + // allow passing a row string if only one non-info tbody exists in the table + valid = typeof $row === 'string' && c.$tbodies.length === 1 && / 0 ) { + cacheIndex += span; + } + cacheIndex++; + } + // add the row data to the end + cells[ c.columns ] = rowData; + // update cache + c.cache[ tbodyIndex ].normalized[ order ] = cells; + } + // resort using current settings + ts.checkResort( c, resort, callback ); + } + }, + + updateCache : function( c, callback, $tbodies ) { + // rebuild parsers + if ( !( c.parsers && c.parsers.length ) ) { + ts.setupParsers( c, $tbodies ); + } + // rebuild the cache map + ts.buildCache( c, callback, $tbodies ); + }, + + // init flag (true) used by pager plugin to prevent widget application + // renamed from appendToTable + appendCache : function( c, init ) { + var parsed, totalRows, $tbody, $curTbody, rowIndex, tbodyIndex, appendTime, + table = c.table, + wo = c.widgetOptions, + $tbodies = c.$tbodies, + rows = [], + cache = c.cache; + // empty table - fixes #206/#346 + if ( ts.isEmptyObject( cache ) ) { + // run pager appender in case the table was just emptied + return c.appender ? c.appender( table, rows ) : + table.isUpdating ? c.$table.triggerHandler( 'updateComplete', table ) : ''; // Fixes #532 + } + if ( c.debug ) { + appendTime = new Date(); + } + for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = $tbodies.eq( tbodyIndex ); + if ( $tbody.length ) { + // detach tbody for manipulation + $curTbody = ts.processTbody( table, $tbody, true ); + parsed = cache[ tbodyIndex ].normalized; + totalRows = parsed.length; + for ( rowIndex = 0; rowIndex < totalRows; rowIndex++ ) { + rows[rows.length] = parsed[ rowIndex ][ c.columns ].$row; + // removeRows used by the pager plugin; don't render if using ajax - fixes #411 + if ( !c.appender || ( c.pager && ( !c.pager.removeRows || !wo.pager_removeRows ) && !c.pager.ajax ) ) { + $curTbody.append( parsed[ rowIndex ][ c.columns ].$row ); + } + } + // restore tbody + ts.processTbody( table, $curTbody, false ); + } + } + if ( c.appender ) { + c.appender( table, rows ); + } + if ( c.debug ) { + console.log( 'Rebuilt table' + ts.benchmark( appendTime ) ); + } + // apply table widgets; but not before ajax completes + if ( !init && !c.appender ) { + ts.applyWidget( table ); + } + if ( table.isUpdating ) { + c.$table.triggerHandler( 'updateComplete', table ); + } + }, + + commonUpdate : function( c, resort, callback ) { + // remove rows/elements before update + c.$table.find( c.selectorRemove ).remove(); + // rebuild parsers + ts.setupParsers( c ); + // rebuild the cache map + ts.buildCache( c ); + ts.checkResort( c, resort, callback ); + }, + + /* + ▄█████ ▄████▄ █████▄ ██████ ██ █████▄ ▄████▄ + ▀█▄ ██ ██ ██▄▄██ ██ ██ ██ ██ ██ ▄▄▄ + ▀█▄ ██ ██ ██▀██ ██ ██ ██ ██ ██ ▀██ + █████▀ ▀████▀ ██ ██ ██ ██ ██ ██ ▀████▀ + */ + initSort : function( c, cell, event ) { + if ( c.table.isUpdating ) { + // let any updates complete before initializing a sort + return setTimeout( function(){ + ts.initSort( c, cell, event ); + }, 50 ); + } + + var arry, indx, headerIndx, dir, temp, tmp, $header, + notMultiSort = !event[ c.sortMultiSortKey ], + table = c.table, + len = c.$headers.length, + // get current column index + col = parseInt( $( cell ).attr( 'data-column' ), 10 ), + order = c.sortVars[ col ].order; + + // Only call sortStart if sorting is enabled + c.$table.triggerHandler( 'sortStart', table ); + // get current column sort order + c.sortVars[ col ].count = + event[ c.sortResetKey ] ? 2 : ( c.sortVars[ col ].count + 1 ) % ( c.sortReset ? 3 : 2 ); + // reset all sorts on non-current column - issue #30 + if ( c.sortRestart ) { + for ( headerIndx = 0; headerIndx < len; headerIndx++ ) { + $header = c.$headers.eq( headerIndx ); + tmp = parseInt( $header.attr( 'data-column' ), 10 ); + // only reset counts on columns that weren't just clicked on and if not included in a multisort + if ( col !== tmp && ( notMultiSort || $header.hasClass( ts.css.sortNone ) ) ) { + c.sortVars[ tmp ].count = -1; + } + } + } + // user only wants to sort on one column + if ( notMultiSort ) { + // flush the sort list + c.sortList = []; + c.last.sortList = []; + if ( c.sortForce !== null ) { + arry = c.sortForce; + for ( indx = 0; indx < arry.length; indx++ ) { + if ( arry[ indx ][ 0 ] !== col ) { + c.sortList[ c.sortList.length ] = arry[ indx ]; + } + } + } + // add column to sort list + dir = order[ c.sortVars[ col ].count ]; + if ( dir < 2 ) { + c.sortList[ c.sortList.length ] = [ col, dir ]; + // add other columns if header spans across multiple + if ( cell.colSpan > 1 ) { + for ( indx = 1; indx < cell.colSpan; indx++ ) { + c.sortList[ c.sortList.length ] = [ col + indx, dir ]; + // update count on columns in colSpan + c.sortVars[ col + indx ].count = $.inArray( dir, order ); + } + } + } + // multi column sorting + } else { + // get rid of the sortAppend before adding more - fixes issue #115 & #523 + c.sortList = $.extend( [], c.last.sortList ); + + // the user has clicked on an already sorted column + if ( ts.isValueInArray( col, c.sortList ) >= 0 ) { + // reverse the sorting direction + for ( indx = 0; indx < c.sortList.length; indx++ ) { + tmp = c.sortList[ indx ]; + if ( tmp[ 0 ] === col ) { + // order.count seems to be incorrect when compared to cell.count + tmp[ 1 ] = order[ c.sortVars[ col ].count ]; + if ( tmp[1] === 2 ) { + c.sortList.splice( indx, 1 ); + c.sortVars[ col ].count = -1; + } + } + } + } else { + // add column to sort list array + dir = order[ c.sortVars[ col ].count ]; + if ( dir < 2 ) { + c.sortList[ c.sortList.length ] = [ col, dir ]; + // add other columns if header spans across multiple + if ( cell.colSpan > 1 ) { + for ( indx = 1; indx < cell.colSpan; indx++ ) { + c.sortList[ c.sortList.length ] = [ col + indx, dir ]; + // update count on columns in colSpan + c.sortVars[ col + indx ].count = $.inArray( dir, order ); + } + } + } + } + } + // save sort before applying sortAppend + c.last.sortList = $.extend( [], c.sortList ); + if ( c.sortList.length && c.sortAppend ) { + arry = $.isArray( c.sortAppend ) ? c.sortAppend : c.sortAppend[ c.sortList[ 0 ][ 0 ] ]; + if ( !ts.isEmptyObject( arry ) ) { + for ( indx = 0; indx < arry.length; indx++ ) { + if ( arry[ indx ][ 0 ] !== col && ts.isValueInArray( arry[ indx ][ 0 ], c.sortList ) < 0 ) { + dir = arry[ indx ][ 1 ]; + temp = ( '' + dir ).match( /^(a|d|s|o|n)/ ); + if ( temp ) { + tmp = c.sortList[ 0 ][ 1 ]; + switch ( temp[ 0 ] ) { + case 'd' : + dir = 1; + break; + case 's' : + dir = tmp; + break; + case 'o' : + dir = tmp === 0 ? 1 : 0; + break; + case 'n' : + dir = ( tmp + 1 ) % ( c.sortReset ? 3 : 2 ); + break; + default: + dir = 0; + break; + } + } + c.sortList[ c.sortList.length ] = [ arry[ indx ][ 0 ], dir ]; + } + } + } + } + // sortBegin event triggered immediately before the sort + c.$table.triggerHandler( 'sortBegin', table ); + // setTimeout needed so the processing icon shows up + setTimeout( function() { + // set css for headers + ts.setHeadersCss( c ); + ts.multisort( c ); + ts.appendCache( c ); + c.$table.triggerHandler( 'sortBeforeEnd', table ); + c.$table.triggerHandler( 'sortEnd', table ); + }, 1 ); + }, + + // sort multiple columns + multisort : function( c ) { /*jshint loopfunc:true */ + var tbodyIndex, sortTime, colMax, rows, + table = c.table, + dir = 0, + textSorter = c.textSorter || '', + sortList = c.sortList, + sortLen = sortList.length, + len = c.$tbodies.length; + if ( c.serverSideSorting || ts.isEmptyObject( c.cache ) ) { + // empty table - fixes #206/#346 + return; + } + if ( c.debug ) { sortTime = new Date(); } + for ( tbodyIndex = 0; tbodyIndex < len; tbodyIndex++ ) { + colMax = c.cache[ tbodyIndex ].colMax; + rows = c.cache[ tbodyIndex ].normalized; + + rows.sort( function( a, b ) { + var sortIndex, num, col, order, sort, x, y; + // rows is undefined here in IE, so don't use it! + for ( sortIndex = 0; sortIndex < sortLen; sortIndex++ ) { + col = sortList[ sortIndex ][ 0 ]; + order = sortList[ sortIndex ][ 1 ]; + // sort direction, true = asc, false = desc + dir = order === 0; + + if ( c.sortStable && a[ col ] === b[ col ] && sortLen === 1 ) { + return a[ c.columns ].order - b[ c.columns ].order; + } + + // fallback to natural sort since it is more robust + num = /n/i.test( ts.getSortType( c.parsers, col ) ); + if ( num && c.strings[ col ] ) { + // sort strings in numerical columns + if ( typeof ( ts.string[ c.strings[ col ] ] ) === 'boolean' ) { + num = ( dir ? 1 : -1 ) * ( ts.string[ c.strings[ col ] ] ? -1 : 1 ); + } else { + num = ( c.strings[ col ] ) ? ts.string[ c.strings[ col ] ] || 0 : 0; + } + // fall back to built-in numeric sort + // var sort = $.tablesorter['sort' + s]( a[col], b[col], dir, colMax[col], table ); + sort = c.numberSorter ? c.numberSorter( a[ col ], b[ col ], dir, colMax[ col ], table ) : + ts[ 'sortNumeric' + ( dir ? 'Asc' : 'Desc' ) ]( a[ col ], b[ col ], num, colMax[ col ], col, c ); + } else { + // set a & b depending on sort direction + x = dir ? a : b; + y = dir ? b : a; + // text sort function + if ( typeof textSorter === 'function' ) { + // custom OVERALL text sorter + sort = textSorter( x[ col ], y[ col ], dir, col, table ); + } else if ( typeof textSorter === 'object' && textSorter.hasOwnProperty( col ) ) { + // custom text sorter for a SPECIFIC COLUMN + sort = textSorter[ col ]( x[ col ], y[ col ], dir, col, table ); + } else { + // fall back to natural sort + sort = ts[ 'sortNatural' + ( dir ? 'Asc' : 'Desc' ) ]( a[ col ], b[ col ], col, c ); + } + } + if ( sort ) { return sort; } + } + return a[ c.columns ].order - b[ c.columns ].order; + }); + } + if ( c.debug ) { + console.log( 'Applying sort ' + sortList.toString() + ts.benchmark( sortTime ) ); + } + }, + + resortComplete : function( c, callback ) { + if ( c.table.isUpdating ) { + c.$table.triggerHandler( 'updateComplete', c.table ); + } + if ( $.isFunction( callback ) ) { + callback( c.table ); + } + }, + + checkResort : function( c, resort, callback ) { + var sortList = $.isArray( resort ) ? resort : c.sortList, + // if no resort parameter is passed, fallback to config.resort (true by default) + resrt = typeof resort === 'undefined' ? c.resort : resort; + // don't try to resort if the table is still processing + // this will catch spamming of the updateCell method + if ( resrt !== false && !c.serverSideSorting && !c.table.isProcessing ) { + if ( sortList.length ) { + ts.sortOn( c, sortList, function() { + ts.resortComplete( c, callback ); + }, true ); + } else { + ts.sortReset( c, function() { + ts.resortComplete( c, callback ); + ts.applyWidget( c.table, false ); + } ); + } + } else { + ts.resortComplete( c, callback ); + ts.applyWidget( c.table, false ); + } + }, + + sortOn : function( c, list, callback, init ) { + var table = c.table; + c.$table.triggerHandler( 'sortStart', table ); + // update header count index + ts.updateHeaderSortCount( c, list ); + // set css for headers + ts.setHeadersCss( c ); + // fixes #346 + if ( c.delayInit && ts.isEmptyObject( c.cache ) ) { + ts.buildCache( c ); + } + c.$table.triggerHandler( 'sortBegin', table ); + // sort the table and append it to the dom + ts.multisort( c ); + ts.appendCache( c, init ); + c.$table.triggerHandler( 'sortBeforeEnd', table ); + c.$table.triggerHandler( 'sortEnd', table ); + ts.applyWidget( table ); + if ( $.isFunction( callback ) ) { + callback( table ); + } + }, + + sortReset : function( c, callback ) { + c.sortList = []; + ts.setHeadersCss( c ); + ts.multisort( c ); + ts.appendCache( c ); + if ( $.isFunction( callback ) ) { + callback( c.table ); + } + }, + + getSortType : function( parsers, column ) { + return ( parsers && parsers[ column ] ) ? parsers[ column ].type || '' : ''; + }, + + getOrder : function( val ) { + // look for 'd' in 'desc' order; return true + return ( /^d/i.test( val ) || val === 1 ); + }, + + // Natural sort - https://github.com/overset/javascript-natural-sort (date sorting removed) + // this function will only accept strings, or you'll see 'TypeError: undefined is not a function' + // I could add a = a.toString(); b = b.toString(); but it'll slow down the sort overall + sortNatural : function( a, b ) { + if ( a === b ) { return 0; } + var aNum, bNum, aFloat, bFloat, indx, max, + regex = ts.regex; + // first try and sort Hex codes + if ( regex.hex.test( b ) ) { + aNum = parseInt( a.match( regex.hex ), 16 ); + bNum = parseInt( b.match( regex.hex ), 16 ); + if ( aNum < bNum ) { return -1; } + if ( aNum > bNum ) { return 1; } + } + // chunk/tokenize + aNum = a.replace( regex.chunk, '\\0$1\\0' ).replace( regex.chunks, '' ).split( '\\0' ); + bNum = b.replace( regex.chunk, '\\0$1\\0' ).replace( regex.chunks, '' ).split( '\\0' ); + max = Math.max( aNum.length, bNum.length ); + // natural sorting through split numeric strings and default strings + for ( indx = 0; indx < max; indx++ ) { + // find floats not starting with '0', string or 0 if not defined + aFloat = isNaN( aNum[ indx ] ) ? aNum[ indx ] || 0 : parseFloat( aNum[ indx ] ) || 0; + bFloat = isNaN( bNum[ indx ] ) ? bNum[ indx ] || 0 : parseFloat( bNum[ indx ] ) || 0; + // handle numeric vs string comparison - number < string - (Kyle Adams) + if ( isNaN( aFloat ) !== isNaN( bFloat ) ) { return isNaN( aFloat ) ? 1 : -1; } + // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2' + if ( typeof aFloat !== typeof bFloat ) { + aFloat += ''; + bFloat += ''; + } + if ( aFloat < bFloat ) { return -1; } + if ( aFloat > bFloat ) { return 1; } + } + return 0; + }, + + sortNaturalAsc : function( a, b, col, c ) { + if ( a === b ) { return 0; } + var empty = ts.string[ ( c.empties[ col ] || c.emptyTo ) ]; + if ( a === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? -1 : 1 ) : -empty || -1; } + if ( b === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? 1 : -1 ) : empty || 1; } + return ts.sortNatural( a, b ); + }, + + sortNaturalDesc : function( a, b, col, c ) { + if ( a === b ) { return 0; } + var empty = ts.string[ ( c.empties[ col ] || c.emptyTo ) ]; + if ( a === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? -1 : 1 ) : empty || 1; } + if ( b === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? 1 : -1 ) : -empty || -1; } + return ts.sortNatural( b, a ); + }, + + // basic alphabetical sort + sortText : function( a, b ) { + return a > b ? 1 : ( a < b ? -1 : 0 ); + }, + + // return text string value by adding up ascii value + // so the text is somewhat sorted when using a digital sort + // this is NOT an alphanumeric sort + getTextValue : function( val, num, max ) { + if ( max ) { + // make sure the text value is greater than the max numerical value (max) + var indx, + len = val ? val.length : 0, + n = max + num; + for ( indx = 0; indx < len; indx++ ) { + n += val.charCodeAt( indx ); + } + return num * n; + } + return 0; + }, + + sortNumericAsc : function( a, b, num, max, col, c ) { + if ( a === b ) { return 0; } + var empty = ts.string[ ( c.empties[ col ] || c.emptyTo ) ]; + if ( a === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? -1 : 1 ) : -empty || -1; } + if ( b === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? 1 : -1 ) : empty || 1; } + if ( isNaN( a ) ) { a = ts.getTextValue( a, num, max ); } + if ( isNaN( b ) ) { b = ts.getTextValue( b, num, max ); } + return a - b; + }, + + sortNumericDesc : function( a, b, num, max, col, c ) { + if ( a === b ) { return 0; } + var empty = ts.string[ ( c.empties[ col ] || c.emptyTo ) ]; + if ( a === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? -1 : 1 ) : empty || 1; } + if ( b === '' && empty !== 0 ) { return typeof empty === 'boolean' ? ( empty ? 1 : -1 ) : -empty || -1; } + if ( isNaN( a ) ) { a = ts.getTextValue( a, num, max ); } + if ( isNaN( b ) ) { b = ts.getTextValue( b, num, max ); } + return b - a; + }, + + sortNumeric : function( a, b ) { + return a - b; + }, + + /* + ██ ██ ██ ██ █████▄ ▄████▄ ██████ ██████ ▄█████ + ██ ██ ██ ██ ██ ██ ██ ▄▄▄ ██▄▄ ██ ▀█▄ + ██ ██ ██ ██ ██ ██ ██ ▀██ ██▀▀ ██ ▀█▄ + ███████▀ ██ █████▀ ▀████▀ ██████ ██ █████▀ + */ + addWidget : function( widget ) { + if ( widget.id && !ts.isEmptyObject( ts.getWidgetById( widget.id ) ) ) { + console.warn( '"' + widget.id + '" widget was loaded more than once!' ); + } + ts.widgets[ ts.widgets.length ] = widget; + }, + + hasWidget : function( $table, name ) { + $table = $( $table ); + return $table.length && $table[ 0 ].config && $table[ 0 ].config.widgetInit[ name ] || false; + }, + + getWidgetById : function( name ) { + var indx, widget, + len = ts.widgets.length; + for ( indx = 0; indx < len; indx++ ) { + widget = ts.widgets[ indx ]; + if ( widget && widget.id && widget.id.toLowerCase() === name.toLowerCase() ) { + return widget; + } + } + }, + + applyWidgetOptions : function( table ) { + var indx, widget, + c = table.config, + len = c.widgets.length; + if ( len ) { + for ( indx = 0; indx < len; indx++ ) { + widget = ts.getWidgetById( c.widgets[ indx ] ); + if ( widget && widget.options ) { + c.widgetOptions = $.extend( true, {}, widget.options, c.widgetOptions ); + } + } + } + }, + + addWidgetFromClass : function( table ) { + var len, indx, + c = table.config, + // look for widgets to apply from table class + // don't match from 'ui-widget-content'; use \S instead of \w to include widgets + // with dashes in the name, e.g. "widget-test-2" extracts out "test-2" + regex = '^' + c.widgetClass.replace( ts.regex.templateName, '(\\S+)+' ) + '$', + widgetClass = new RegExp( regex, 'g' ), + // split up table class (widget id's can include dashes) - stop using match + // otherwise only one widget gets extracted, see #1109 + widgets = ( table.className || '' ).split( ts.regex.spaces ); + if ( widgets.length ) { + len = widgets.length; + for ( indx = 0; indx < len; indx++ ) { + if ( widgets[ indx ].match( widgetClass ) ) { + c.widgets[ c.widgets.length ] = widgets[ indx ].replace( widgetClass, '$1' ); + } + } + } + }, + + applyWidgetId : function( table, id, init ) { + table = $(table)[0]; + var applied, time, name, + c = table.config, + wo = c.widgetOptions, + widget = ts.getWidgetById( id ); + if ( widget ) { + name = widget.id; + applied = false; + // add widget name to option list so it gets reapplied after sorting, filtering, etc + if ( $.inArray( name, c.widgets ) < 0 ) { + c.widgets[ c.widgets.length ] = name; + } + if ( c.debug ) { time = new Date(); } + + if ( init || !( c.widgetInit[ name ] ) ) { + // set init flag first to prevent calling init more than once (e.g. pager) + c.widgetInit[ name ] = true; + if ( table.hasInitialized ) { + // don't reapply widget options on tablesorter init + ts.applyWidgetOptions( table ); + } + if ( typeof widget.init === 'function' ) { + applied = true; + if ( c.debug ) { + console[ console.group ? 'group' : 'log' ]( 'Initializing ' + name + ' widget' ); + } + widget.init( table, widget, c, wo ); + } + } + if ( !init && typeof widget.format === 'function' ) { + applied = true; + if ( c.debug ) { + console[ console.group ? 'group' : 'log' ]( 'Updating ' + name + ' widget' ); + } + widget.format( table, c, wo, false ); + } + if ( c.debug ) { + if ( applied ) { + console.log( 'Completed ' + ( init ? 'initializing ' : 'applying ' ) + name + ' widget' + ts.benchmark( time ) ); + if ( console.groupEnd ) { console.groupEnd(); } + } + } + } + }, + + applyWidget : function( table, init, callback ) { + table = $( table )[ 0 ]; // in case this is called externally + var indx, len, names, widget, time, + c = table.config, + widgets = []; + // prevent numerous consecutive widget applications + if ( init !== false && table.hasInitialized && ( table.isApplyingWidgets || table.isUpdating ) ) { + return; + } + if ( c.debug ) { time = new Date(); } + ts.addWidgetFromClass( table ); + // prevent "tablesorter-ready" from firing multiple times in a row + clearTimeout( c.timerReady ); + if ( c.widgets.length ) { + table.isApplyingWidgets = true; + // ensure unique widget ids + c.widgets = $.grep( c.widgets, function( val, index ) { + return $.inArray( val, c.widgets ) === index; + }); + names = c.widgets || []; + len = names.length; + // build widget array & add priority as needed + for ( indx = 0; indx < len; indx++ ) { + widget = ts.getWidgetById( names[ indx ] ); + if ( widget && widget.id ) { + // set priority to 10 if not defined + if ( !widget.priority ) { widget.priority = 10; } + widgets[ indx ] = widget; + } else if ( c.debug ) { + console.warn( '"' + names[ indx ] + '" widget code does not exist!' ); + } + } + // sort widgets by priority + widgets.sort( function( a, b ) { + return a.priority < b.priority ? -1 : a.priority === b.priority ? 0 : 1; + }); + // add/update selected widgets + len = widgets.length; + if ( c.debug ) { + console[ console.group ? 'group' : 'log' ]( 'Start ' + ( init ? 'initializing' : 'applying' ) + ' widgets' ); + } + for ( indx = 0; indx < len; indx++ ) { + widget = widgets[ indx ]; + if ( widget && widget.id ) { + ts.applyWidgetId( table, widget.id, init ); + } + } + if ( c.debug && console.groupEnd ) { console.groupEnd(); } + // callback executed on init only + if ( !init && typeof callback === 'function' ) { + callback( table ); + } + } + c.timerReady = setTimeout( function() { + table.isApplyingWidgets = false; + $.data( table, 'lastWidgetApplication', new Date() ); + c.$table.triggerHandler( 'tablesorter-ready' ); + }, 10 ); + if ( c.debug ) { + widget = c.widgets.length; + console.log( 'Completed ' + + ( init === true ? 'initializing ' : 'applying ' ) + widget + + ' widget' + ( widget !== 1 ? 's' : '' ) + ts.benchmark( time ) ); + } + }, + + removeWidget : function( table, name, refreshing ) { + table = $( table )[ 0 ]; + var index, widget, indx, len, + c = table.config; + // if name === true, add all widgets from $.tablesorter.widgets + if ( name === true ) { + name = []; + len = ts.widgets.length; + for ( indx = 0; indx < len; indx++ ) { + widget = ts.widgets[ indx ]; + if ( widget && widget.id ) { + name[ name.length ] = widget.id; + } + } + } else { + // name can be either an array of widgets names, + // or a space/comma separated list of widget names + name = ( $.isArray( name ) ? name.join( ',' ) : name || '' ).toLowerCase().split( /[\s,]+/ ); + } + len = name.length; + for ( index = 0; index < len; index++ ) { + widget = ts.getWidgetById( name[ index ] ); + indx = $.inArray( name[ index ], c.widgets ); + // don't remove the widget from config.widget if refreshing + if ( indx >= 0 && refreshing !== true ) { + c.widgets.splice( indx, 1 ); + } + if ( widget && widget.remove ) { + if ( c.debug ) { + console.log( ( refreshing ? 'Refreshing' : 'Removing' ) + ' "' + name[ index ] + '" widget' ); + } + widget.remove( table, c, c.widgetOptions, refreshing ); + c.widgetInit[ name[ index ] ] = false; + } + } + }, + + refreshWidgets : function( table, doAll, dontapply ) { + table = $( table )[ 0 ]; // see issue #243 + var indx, widget, + c = table.config, + curWidgets = c.widgets, + widgets = ts.widgets, + len = widgets.length, + list = [], + callback = function( table ) { + $( table ).triggerHandler( 'refreshComplete' ); + }; + // remove widgets not defined in config.widgets, unless doAll is true + for ( indx = 0; indx < len; indx++ ) { + widget = widgets[ indx ]; + if ( widget && widget.id && ( doAll || $.inArray( widget.id, curWidgets ) < 0 ) ) { + list[ list.length ] = widget.id; + } + } + ts.removeWidget( table, list.join( ',' ), true ); + if ( dontapply !== true ) { + // call widget init if + ts.applyWidget( table, doAll || false, callback ); + if ( doAll ) { + // apply widget format + ts.applyWidget( table, false, callback ); + } + } else { + callback( table ); + } + }, + + /* + ██ ██ ██████ ██ ██ ██ ██████ ██ ██████ ▄█████ + ██ ██ ██ ██ ██ ██ ██ ██ ██▄▄ ▀█▄ + ██ ██ ██ ██ ██ ██ ██ ██ ██▀▀ ▀█▄ + ▀████▀ ██ ██ ██████ ██ ██ ██ ██████ █████▀ + */ + benchmark : function( diff ) { + return ( ' ( ' + ( new Date().getTime() - diff.getTime() ) + 'ms )' ); + }, + // deprecated ts.log + log : function() { + console.log( arguments ); + }, + + // $.isEmptyObject from jQuery v1.4 + isEmptyObject : function( obj ) { + /*jshint forin: false */ + for ( var name in obj ) { + return false; + } + return true; + }, + + isValueInArray : function( column, arry ) { + var indx, + len = arry && arry.length || 0; + for ( indx = 0; indx < len; indx++ ) { + if ( arry[ indx ][ 0 ] === column ) { + return indx; + } + } + return -1; + }, + + formatFloat : function( str, table ) { + if ( typeof str !== 'string' || str === '' ) { return str; } + // allow using formatFloat without a table; defaults to US number format + var num, + usFormat = table && table.config ? table.config.usNumberFormat !== false : + typeof table !== 'undefined' ? table : true; + if ( usFormat ) { + // US Format - 1,234,567.89 -> 1234567.89 + str = str.replace( ts.regex.comma, '' ); + } else { + // German Format = 1.234.567,89 -> 1234567.89 + // French Format = 1 234 567,89 -> 1234567.89 + str = str.replace( ts.regex.digitNonUS, '' ).replace( ts.regex.comma, '.' ); + } + if ( ts.regex.digitNegativeTest.test( str ) ) { + // make (#) into a negative number -> (10) = -10 + str = str.replace( ts.regex.digitNegativeReplace, '-$1' ); + } + num = parseFloat( str ); + // return the text instead of zero + return isNaN( num ) ? $.trim( str ) : num; + }, + + isDigit : function( str ) { + // replace all unwanted chars and match + return isNaN( str ) ? + ts.regex.digitTest.test( str.toString().replace( ts.regex.digitReplace, '' ) ) : + str !== ''; + }, + + // computeTableHeaderCellIndexes from: + // http://www.javascripttoolbox.com/lib/table/examples.php + // http://www.javascripttoolbox.com/temp/table_cellindex.html + computeColumnIndex : function( $rows, c ) { + var i, j, k, l, cell, cells, rowIndex, rowSpan, colSpan, firstAvailCol, + // total columns has been calculated, use it to set the matrixrow + columns = c && c.columns || 0, + matrix = [], + matrixrow = new Array( columns ); + for ( i = 0; i < $rows.length; i++ ) { + cells = $rows[ i ].cells; + for ( j = 0; j < cells.length; j++ ) { + cell = cells[ j ]; + rowIndex = cell.parentNode.rowIndex; + rowSpan = cell.rowSpan || 1; + colSpan = cell.colSpan || 1; + if ( typeof matrix[ rowIndex ] === 'undefined' ) { + matrix[ rowIndex ] = []; + } + // Find first available column in the first row + for ( k = 0; k < matrix[ rowIndex ].length + 1; k++ ) { + if ( typeof matrix[ rowIndex ][ k ] === 'undefined' ) { + firstAvailCol = k; + break; + } + } + // jscs:disable disallowEmptyBlocks + if ( columns && cell.cellIndex === firstAvailCol ) { + // don't to anything + } else if ( cell.setAttribute ) { + // jscs:enable disallowEmptyBlocks + // add data-column (setAttribute = IE8+) + cell.setAttribute( 'data-column', firstAvailCol ); + } else { + // remove once we drop support for IE7 - 1/12/2016 + $( cell ).attr( 'data-column', firstAvailCol ); + } + for ( k = rowIndex; k < rowIndex + rowSpan; k++ ) { + if ( typeof matrix[ k ] === 'undefined' ) { + matrix[ k ] = []; + } + matrixrow = matrix[ k ]; + for ( l = firstAvailCol; l < firstAvailCol + colSpan; l++ ) { + matrixrow[ l ] = 'x'; + } + } + } + } + return matrixrow.length; + }, + + // automatically add a colgroup with col elements set to a percentage width + fixColumnWidth : function( table ) { + table = $( table )[ 0 ]; + var overallWidth, percent, $tbodies, len, index, + c = table.config, + $colgroup = c.$table.children( 'colgroup' ); + // remove plugin-added colgroup, in case we need to refresh the widths + if ( $colgroup.length && $colgroup.hasClass( ts.css.colgroup ) ) { + $colgroup.remove(); + } + if ( c.widthFixed && c.$table.children( 'colgroup' ).length === 0 ) { + $colgroup = $( '' ); + overallWidth = c.$table.width(); + // only add col for visible columns - fixes #371 + $tbodies = c.$tbodies.find( 'tr:first' ).children( ':visible' ); + len = $tbodies.length; + for ( index = 0; index < len; index++ ) { + percent = parseInt( ( $tbodies.eq( index ).width() / overallWidth ) * 1000, 10 ) / 10 + '%'; + $colgroup.append( $( '' ).css( 'width', percent ) ); + } + c.$table.prepend( $colgroup ); + } + }, + + // get sorter, string, empty, etc options for each column from + // jQuery data, metadata, header option or header class name ('sorter-false') + // priority = jQuery data > meta > headers option > header class name + getData : function( header, configHeader, key ) { + var meta, cl4ss, + val = '', + $header = $( header ); + if ( !$header.length ) { return ''; } + meta = $.metadata ? $header.metadata() : false; + cl4ss = ' ' + ( $header.attr( 'class' ) || '' ); + if ( typeof $header.data( key ) !== 'undefined' || + typeof $header.data( key.toLowerCase() ) !== 'undefined' ) { + // 'data-lockedOrder' is assigned to 'lockedorder'; but 'data-locked-order' is assigned to 'lockedOrder' + // 'data-sort-initial-order' is assigned to 'sortInitialOrder' + val += $header.data( key ) || $header.data( key.toLowerCase() ); + } else if ( meta && typeof meta[ key ] !== 'undefined' ) { + val += meta[ key ]; + } else if ( configHeader && typeof configHeader[ key ] !== 'undefined' ) { + val += configHeader[ key ]; + } else if ( cl4ss !== ' ' && cl4ss.match( ' ' + key + '-' ) ) { + // include sorter class name 'sorter-text', etc; now works with 'sorter-my-custom-parser' + val = cl4ss.match( new RegExp( '\\s' + key + '-([\\w-]+)' ) )[ 1 ] || ''; + } + return $.trim( val ); + }, + + getColumnData : function( table, obj, indx, getCell, $headers ) { + if ( typeof obj === 'undefined' || obj === null ) { return; } + table = $( table )[ 0 ]; + var $header, key, + c = table.config, + $cells = ( $headers || c.$headers ), + // c.$headerIndexed is not defined initially + $cell = c.$headerIndexed && c.$headerIndexed[ indx ] || + $cells.filter( '[data-column="' + indx + '"]:last' ); + if ( obj[ indx ] ) { + return getCell ? obj[ indx ] : obj[ $cells.index( $cell ) ]; + } + for ( key in obj ) { + if ( typeof key === 'string' ) { + $header = $cell + // header cell with class/id + .filter( key ) + // find elements within the header cell with cell/id + .add( $cell.find( key ) ); + if ( $header.length ) { + return obj[ key ]; + } + } + } + return; + }, + + // *** Process table *** + // add processing indicator + isProcessing : function( $table, toggle, $headers ) { + $table = $( $table ); + var c = $table[ 0 ].config, + // default to all headers + $header = $headers || $table.find( '.' + ts.css.header ); + if ( toggle ) { + // don't use sortList if custom $headers used + if ( typeof $headers !== 'undefined' && c.sortList.length > 0 ) { + // get headers from the sortList + $header = $header.filter( function() { + // get data-column from attr to keep compatibility with jQuery 1.2.6 + return this.sortDisabled ? + false : + ts.isValueInArray( parseFloat( $( this ).attr( 'data-column' ) ), c.sortList ) >= 0; + }); + } + $table.add( $header ).addClass( ts.css.processing + ' ' + c.cssProcessing ); + } else { + $table.add( $header ).removeClass( ts.css.processing + ' ' + c.cssProcessing ); + } + }, + + // detach tbody but save the position + // don't use tbody because there are portions that look for a tbody index (updateCell) + processTbody : function( table, $tb, getIt ) { + table = $( table )[ 0 ]; + if ( getIt ) { + table.isProcessing = true; + $tb.before( '' ); + return $.fn.detach ? $tb.detach() : $tb.remove(); + } + var holdr = $( table ).find( 'colgroup.tablesorter-savemyplace' ); + $tb.insertAfter( holdr ); + holdr.remove(); + table.isProcessing = false; + }, + + clearTableBody : function( table ) { + $( table )[ 0 ].config.$tbodies.children().detach(); + }, + + // used when replacing accented characters during sorting + characterEquivalents : { + 'a' : '\u00e1\u00e0\u00e2\u00e3\u00e4\u0105\u00e5', // áàâãäąå + 'A' : '\u00c1\u00c0\u00c2\u00c3\u00c4\u0104\u00c5', // ÁÀÂÃÄĄÅ + 'c' : '\u00e7\u0107\u010d', // çćč + 'C' : '\u00c7\u0106\u010c', // ÇĆČ + 'e' : '\u00e9\u00e8\u00ea\u00eb\u011b\u0119', // éèêëěę + 'E' : '\u00c9\u00c8\u00ca\u00cb\u011a\u0118', // ÉÈÊËĚĘ + 'i' : '\u00ed\u00ec\u0130\u00ee\u00ef\u0131', // íìİîïı + 'I' : '\u00cd\u00cc\u0130\u00ce\u00cf', // ÍÌİÎÏ + 'o' : '\u00f3\u00f2\u00f4\u00f5\u00f6\u014d', // óòôõöō + 'O' : '\u00d3\u00d2\u00d4\u00d5\u00d6\u014c', // ÓÒÔÕÖŌ + 'ss': '\u00df', // ß (s sharp) + 'SS': '\u1e9e', // ẞ (Capital sharp s) + 'u' : '\u00fa\u00f9\u00fb\u00fc\u016f', // úùûüů + 'U' : '\u00da\u00d9\u00db\u00dc\u016e' // ÚÙÛÜŮ + }, + + replaceAccents : function( str ) { + var chr, + acc = '[', + eq = ts.characterEquivalents; + if ( !ts.characterRegex ) { + ts.characterRegexArray = {}; + for ( chr in eq ) { + if ( typeof chr === 'string' ) { + acc += eq[ chr ]; + ts.characterRegexArray[ chr ] = new RegExp( '[' + eq[ chr ] + ']', 'g' ); + } + } + ts.characterRegex = new RegExp( acc + ']' ); + } + if ( ts.characterRegex.test( str ) ) { + for ( chr in eq ) { + if ( typeof chr === 'string' ) { + str = str.replace( ts.characterRegexArray[ chr ], chr ); + } + } + } + return str; + }, + + // restore headers + restoreHeaders : function( table ) { + var index, $cell, + c = $( table )[ 0 ].config, + $headers = c.$table.find( c.selectorHeaders ), + len = $headers.length; + // don't use c.$headers here in case header cells were swapped + for ( index = 0; index < len; index++ ) { + $cell = $headers.eq( index ); + // only restore header cells if it is wrapped + // because this is also used by the updateAll method + if ( $cell.find( '.' + ts.css.headerIn ).length ) { + $cell.html( c.headerContent[ index ] ); + } + } + }, + + destroy : function( table, removeClasses, callback ) { + table = $( table )[ 0 ]; + if ( !table.hasInitialized ) { return; } + // remove all widgets + ts.removeWidget( table, true, false ); + var events, + $t = $( table ), + c = table.config, + debug = c.debug, + $h = $t.find( 'thead:first' ), + $r = $h.find( 'tr.' + ts.css.headerRow ).removeClass( ts.css.headerRow + ' ' + c.cssHeaderRow ), + $f = $t.find( 'tfoot:first > tr' ).children( 'th, td' ); + if ( removeClasses === false && $.inArray( 'uitheme', c.widgets ) >= 0 ) { + // reapply uitheme classes, in case we want to maintain appearance + $t.triggerHandler( 'applyWidgetId', [ 'uitheme' ] ); + $t.triggerHandler( 'applyWidgetId', [ 'zebra' ] ); + } + // remove widget added rows, just in case + $h.find( 'tr' ).not( $r ).remove(); + // disable tablesorter - not using .unbind( namespace ) because namespacing was + // added in jQuery v1.4.3 - see http://api.jquery.com/event.namespace/ + events = 'sortReset update updateRows updateAll updateHeaders updateCell addRows updateComplete sorton ' + + 'appendCache updateCache applyWidgetId applyWidgets refreshWidgets removeWidget destroy mouseup mouseleave ' + + 'keypress sortBegin sortEnd resetToLoadState '.split( ' ' ) + .join( c.namespace + ' ' ); + $t + .removeData( 'tablesorter' ) + .unbind( events.replace( ts.regex.spaces, ' ' ) ); + c.$headers + .add( $f ) + .removeClass( [ ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone ].join( ' ' ) ) + .removeAttr( 'data-column' ) + .removeAttr( 'aria-label' ) + .attr( 'aria-disabled', 'true' ); + $r + .find( c.selectorSort ) + .unbind( ( 'mousedown mouseup keypress '.split( ' ' ).join( c.namespace + ' ' ) ).replace( ts.regex.spaces, ' ' ) ); + ts.restoreHeaders( table ); + $t.toggleClass( ts.css.table + ' ' + c.tableClass + ' tablesorter-' + c.theme, removeClasses === false ); + // clear flag in case the plugin is initialized again + table.hasInitialized = false; + delete table.config.cache; + if ( typeof callback === 'function' ) { + callback( table ); + } + if ( debug ) { + console.log( 'tablesorter has been removed' ); + } + } + + }; + + $.fn.tablesorter = function( settings ) { + return this.each( function() { + var table = this, + // merge & extend config options + c = $.extend( true, {}, ts.defaults, settings, ts.instanceMethods ); + // save initial settings + c.originalSettings = settings; + // create a table from data (build table widget) + if ( !table.hasInitialized && ts.buildTable && this.nodeName !== 'TABLE' ) { + // return the table (in case the original target is the table's container) + ts.buildTable( table, c ); + } else { + ts.setup( table, c ); + } + }); + }; + + // set up debug logs + if ( !( window.console && window.console.log ) ) { + // access $.tablesorter.logs for browsers that don't have a console... + ts.logs = []; + /*jshint -W020 */ + console = {}; + console.log = console.warn = console.error = console.table = function() { + var arg = arguments.length > 1 ? arguments : arguments[0]; + ts.logs[ ts.logs.length ] = { date: Date.now(), log: arg }; + }; + } + + // add default parsers + ts.addParser({ + id : 'no-parser', + is : function() { + return false; + }, + format : function() { + return ''; + }, + type : 'text' + }); + + ts.addParser({ + id : 'text', + is : function() { + return true; + }, + format : function( str, table ) { + var c = table.config; + if ( str ) { + str = $.trim( c.ignoreCase ? str.toLocaleLowerCase() : str ); + str = c.sortLocaleCompare ? ts.replaceAccents( str ) : str; + } + return str; + }, + type : 'text' + }); + + ts.regex.nondigit = /[^\w,. \-()]/g; + ts.addParser({ + id : 'digit', + is : function( str ) { + return ts.isDigit( str ); + }, + format : function( str, table ) { + var num = ts.formatFloat( ( str || '' ).replace( ts.regex.nondigit, '' ), table ); + return str && typeof num === 'number' ? num : + str ? $.trim( str && table.config.ignoreCase ? str.toLocaleLowerCase() : str ) : str; + }, + type : 'numeric' + }); + + ts.regex.currencyReplace = /[+\-,. ]/g; + ts.regex.currencyTest = /^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/; + ts.addParser({ + id : 'currency', + is : function( str ) { + str = ( str || '' ).replace( ts.regex.currencyReplace, '' ); + // test for £$€¤¥¢ + return ts.regex.currencyTest.test( str ); + }, + format : function( str, table ) { + var num = ts.formatFloat( ( str || '' ).replace( ts.regex.nondigit, '' ), table ); + return str && typeof num === 'number' ? num : + str ? $.trim( str && table.config.ignoreCase ? str.toLocaleLowerCase() : str ) : str; + }, + type : 'numeric' + }); + + // too many protocols to add them all https://en.wikipedia.org/wiki/URI_scheme + // now, this regex can be updated before initialization + ts.regex.urlProtocolTest = /^(https?|ftp|file):\/\//; + ts.regex.urlProtocolReplace = /(https?|ftp|file):\/\//; + ts.addParser({ + id : 'url', + is : function( str ) { + return ts.regex.urlProtocolTest.test( str ); + }, + format : function( str ) { + return str ? $.trim( str.replace( ts.regex.urlProtocolReplace, '' ) ) : str; + }, + parsed : true, // filter widget flag + type : 'text' + }); + + ts.regex.dash = /-/g; + ts.regex.isoDate = /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/; + ts.addParser({ + id : 'isoDate', + is : function( str ) { + return ts.regex.isoDate.test( str ); + }, + format : function( str, table ) { + var date = str ? new Date( str.replace( ts.regex.dash, '/' ) ) : str; + return date instanceof Date && isFinite( date ) ? date.getTime() : str; + }, + type : 'numeric' + }); + + ts.regex.percent = /%/g; + ts.regex.percentTest = /(\d\s*?%|%\s*?\d)/; + ts.addParser({ + id : 'percent', + is : function( str ) { + return ts.regex.percentTest.test( str ) && str.length < 15; + }, + format : function( str, table ) { + return str ? ts.formatFloat( str.replace( ts.regex.percent, '' ), table ) : str; + }, + type : 'numeric' + }); + + // added image parser to core v2.17.9 + ts.addParser({ + id : 'image', + is : function( str, table, node, $node ) { + return $node.find( 'img' ).length > 0; + }, + format : function( str, table, cell ) { + return $( cell ).find( 'img' ).attr( table.config.imgAttr || 'alt' ) || str; + }, + parsed : true, // filter widget flag + type : 'text' + }); + + ts.regex.dateReplace = /(\S)([AP]M)$/i; // used by usLongDate & time parser + ts.regex.usLongDateTest1 = /^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i; + ts.regex.usLongDateTest2 = /^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i; + ts.addParser({ + id : 'usLongDate', + is : function( str ) { + // two digit years are not allowed cross-browser + // Jan 01, 2013 12:34:56 PM or 01 Jan 2013 + return ts.regex.usLongDateTest1.test( str ) || ts.regex.usLongDateTest2.test( str ); + }, + format : function( str, table ) { + var date = str ? new Date( str.replace( ts.regex.dateReplace, '$1 $2' ) ) : str; + return date instanceof Date && isFinite( date ) ? date.getTime() : str; + }, + type : 'numeric' + }); + + // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included + ts.regex.shortDateTest = /(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/; + // escaped "-" because JSHint in Firefox was showing it as an error + ts.regex.shortDateReplace = /[\-.,]/g; + // XXY covers MDY & DMY formats + ts.regex.shortDateXXY = /(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/; + ts.regex.shortDateYMD = /(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/; + ts.convertFormat = function( dateString, format ) { + dateString = ( dateString || '' ) + .replace( ts.regex.spaces, ' ' ) + .replace( ts.regex.shortDateReplace, '/' ); + if ( format === 'mmddyyyy' ) { + dateString = dateString.replace( ts.regex.shortDateXXY, '$3/$1/$2' ); + } else if ( format === 'ddmmyyyy' ) { + dateString = dateString.replace( ts.regex.shortDateXXY, '$3/$2/$1' ); + } else if ( format === 'yyyymmdd' ) { + dateString = dateString.replace( ts.regex.shortDateYMD, '$1/$2/$3' ); + } + var date = new Date( dateString ); + return date instanceof Date && isFinite( date ) ? date.getTime() : ''; + }; + + ts.addParser({ + id : 'shortDate', // 'mmddyyyy', 'ddmmyyyy' or 'yyyymmdd' + is : function( str ) { + str = ( str || '' ).replace( ts.regex.spaces, ' ' ).replace( ts.regex.shortDateReplace, '/' ); + return ts.regex.shortDateTest.test( str ); + }, + format : function( str, table, cell, cellIndex ) { + if ( str ) { + var c = table.config, + $header = c.$headerIndexed[ cellIndex ], + format = $header.length && $header.data( 'dateFormat' ) || + ts.getData( $header, ts.getColumnData( table, c.headers, cellIndex ), 'dateFormat' ) || + c.dateFormat; + // save format because getData can be slow... + if ( $header.length ) { + $header.data( 'dateFormat', format ); + } + return ts.convertFormat( str, format ) || str; + } + return str; + }, + type : 'numeric' + }); + + // match 24 hour time & 12 hours time + am/pm - see http://regexr.com/3c3tk + ts.regex.timeTest = /^([1-9]|1[0-2]):([0-5]\d)(\s[AP]M)$|^((?:[01]\d|[2][0-4]):[0-5]\d)$/i; + ts.regex.timeMatch = /([1-9]|1[0-2]):([0-5]\d)(\s[AP]M)|((?:[01]\d|[2][0-4]):[0-5]\d)/i; + ts.addParser({ + id : 'time', + is : function( str ) { + return ts.regex.timeTest.test( str ); + }, + format : function( str, table ) { + // isolate time... ignore month, day and year + var temp, + timePart = ( str || '' ).match( ts.regex.timeMatch ), + orig = new Date( str ), + // no time component? default to 00:00 by leaving it out, but only if str is defined + time = str && ( timePart !== null ? timePart[ 0 ] : '00:00 AM' ), + date = time ? new Date( '2000/01/01 ' + time.replace( ts.regex.dateReplace, '$1 $2' ) ) : time; + if ( date instanceof Date && isFinite( date ) ) { + temp = orig instanceof Date && isFinite( orig ) ? orig.getTime() : 0; + // if original string was a valid date, add it to the decimal so the column sorts in some kind of order + // luckily new Date() ignores the decimals + return temp ? parseFloat( date.getTime() + '.' + orig.getTime() ) : date.getTime(); + } + return str; + }, + type : 'numeric' + }); + + ts.addParser({ + id : 'metadata', + is : function() { + return false; + }, + format : function( str, table, cell ) { + var c = table.config, + p = ( !c.parserMetadataName ) ? 'sortValue' : c.parserMetadataName; + return $( cell ).metadata()[ p ]; + }, + type : 'numeric' + }); + + /* + ██████ ██████ █████▄ █████▄ ▄████▄ + ▄█▀ ██▄▄ ██▄▄██ ██▄▄██ ██▄▄██ + ▄█▀ ██▀▀ ██▀▀██ ██▀▀█ ██▀▀██ + ██████ ██████ █████▀ ██ ██ ██ ██ + */ + // add default widgets + ts.addWidget({ + id : 'zebra', + priority : 90, + format : function( table, c, wo ) { + var $visibleRows, $row, count, isEven, tbodyIndex, rowIndex, len, + child = new RegExp( c.cssChildRow, 'i' ), + $tbodies = c.$tbodies.add( $( c.namespace + '_extra_table' ).children( 'tbody:not(.' + c.cssInfoBlock + ')' ) ); + for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + // loop through the visible rows + count = 0; + $visibleRows = $tbodies.eq( tbodyIndex ).children( 'tr:visible' ).not( c.selectorRemove ); + len = $visibleRows.length; + for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { + $row = $visibleRows.eq( rowIndex ); + // style child rows the same way the parent row was styled + if ( !child.test( $row[ 0 ].className ) ) { count++; } + isEven = ( count % 2 === 0 ); + $row + .removeClass( wo.zebra[ isEven ? 1 : 0 ] ) + .addClass( wo.zebra[ isEven ? 0 : 1 ] ); + } + } + }, + remove : function( table, c, wo, refreshing ) { + if ( refreshing ) { return; } + var tbodyIndex, $tbody, + $tbodies = c.$tbodies, + toRemove = ( wo.zebra || [ 'even', 'odd' ] ).join( ' ' ); + for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ){ + $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody + $tbody.children().removeClass( toRemove ); + ts.processTbody( table, $tbody, false ); // restore tbody + } + } + }); + +})( jQuery ); + +/*! Widget: storage - updated 3/1/2016 (v2.25.5) */ +/*global JSON:false */ +;(function ($, window, document) { + 'use strict'; + + var ts = $.tablesorter || {}; + // *** Store data in local storage, with a cookie fallback *** + /* IE7 needs JSON library for JSON.stringify - (http://caniuse.com/#search=json) + if you need it, then include https://github.com/douglascrockford/JSON-js + + $.parseJSON is not available is jQuery versions older than 1.4.1, using older + versions will only allow storing information for one page at a time + + // *** Save data (JSON format only) *** + // val must be valid JSON... use http://jsonlint.com/ to ensure it is valid + var val = { "mywidget" : "data1" }; // valid JSON uses double quotes + // $.tablesorter.storage(table, key, val); + $.tablesorter.storage(table, 'tablesorter-mywidget', val); + + // *** Get data: $.tablesorter.storage(table, key); *** + v = $.tablesorter.storage(table, 'tablesorter-mywidget'); + // val may be empty, so also check for your data + val = (v && v.hasOwnProperty('mywidget')) ? v.mywidget : ''; + alert(val); // 'data1' if saved, or '' if not + */ + ts.storage = function(table, key, value, options) { + table = $(table)[0]; + var cookieIndex, cookies, date, + hasStorage = false, + values = {}, + c = table.config, + wo = c && c.widgetOptions, + storageType = ( options && options.useSessionStorage ) || ( wo && wo.storage_useSessionStorage ) ? + 'sessionStorage' : 'localStorage', + $table = $(table), + // id from (1) options ID, (2) table 'data-table-group' attribute, (3) widgetOptions.storage_tableId, + // (4) table ID, then (5) table index + id = options && options.id || + $table.attr( options && options.group || wo && wo.storage_group || 'data-table-group') || + wo && wo.storage_tableId || table.id || $('.tablesorter').index( $table ), + // url from (1) options url, (2) table 'data-table-page' attribute, (3) widgetOptions.storage_fixedUrl, + // (4) table.config.fixedUrl (deprecated), then (5) window location path + url = options && options.url || + $table.attr(options && options.page || wo && wo.storage_page || 'data-table-page') || + wo && wo.storage_fixedUrl || c && c.fixedUrl || window.location.pathname; + // https://gist.github.com/paulirish/5558557 + if (storageType in window) { + try { + window[storageType].setItem('_tmptest', 'temp'); + hasStorage = true; + window[storageType].removeItem('_tmptest'); + } catch (error) { + if (c && c.debug) { + console.warn( storageType + ' is not supported in this browser' ); + } + } + } + // *** get value *** + if ($.parseJSON) { + if (hasStorage) { + values = $.parseJSON( window[storageType][key] || 'null' ) || {}; + } else { + // old browser, using cookies + cookies = document.cookie.split(/[;\s|=]/); + // add one to get from the key to the value + cookieIndex = $.inArray(key, cookies) + 1; + values = (cookieIndex !== 0) ? $.parseJSON(cookies[cookieIndex] || 'null') || {} : {}; + } + } + // allow value to be an empty string too + if (typeof value !== 'undefined' && window.JSON && JSON.hasOwnProperty('stringify')) { + // add unique identifiers = url pathname > table ID/index on page > data + if (!values[url]) { + values[url] = {}; + } + values[url][id] = value; + // *** set value *** + if (hasStorage) { + window[storageType][key] = JSON.stringify(values); + } else { + date = new Date(); + date.setTime(date.getTime() + (31536e+6)); // 365 days + document.cookie = key + '=' + (JSON.stringify(values)).replace(/\"/g, '\"') + '; expires=' + date.toGMTString() + '; path=/'; + } + } else { + return values && values[url] ? values[url][id] : ''; + } + }; + +})(jQuery, window, document); + +/*! Widget: uitheme - updated 3/26/2015 (v2.21.3) */ +;(function ($) { + 'use strict'; + var ts = $.tablesorter || {}; + + ts.themes = { + 'bootstrap' : { + table : 'table table-bordered table-striped', + caption : 'caption', + // header class names + header : 'bootstrap-header', // give the header a gradient background (theme.bootstrap_2.css) + sortNone : '', + sortAsc : '', + sortDesc : '', + active : '', // applied when column is sorted + hover : '', // custom css required - a defined bootstrap style may not override other classes + // icon class names + icons : '', // add 'icon-white' to make them white; this icon class is added to the in the header + iconSortNone : 'bootstrap-icon-unsorted', // class name added to icon when column is not sorted + iconSortAsc : 'icon-chevron-up glyphicon glyphicon-chevron-up', // class name added to icon when column has ascending sort + iconSortDesc : 'icon-chevron-down glyphicon glyphicon-chevron-down', // class name added to icon when column has descending sort + filterRow : '', // filter row class + footerRow : '', + footerCells : '', + even : '', // even row zebra striping + odd : '' // odd row zebra striping + }, + 'jui' : { + table : 'ui-widget ui-widget-content ui-corner-all', // table classes + caption : 'ui-widget-content', + // header class names + header : 'ui-widget-header ui-corner-all ui-state-default', // header classes + sortNone : '', + sortAsc : '', + sortDesc : '', + active : 'ui-state-active', // applied when column is sorted + hover : 'ui-state-hover', // hover class + // icon class names + icons : 'ui-icon', // icon class added to the in the header + iconSortNone : 'ui-icon-carat-2-n-s', // class name added to icon when column is not sorted + iconSortAsc : 'ui-icon-carat-1-n', // class name added to icon when column has ascending sort + iconSortDesc : 'ui-icon-carat-1-s', // class name added to icon when column has descending sort + filterRow : '', + footerRow : '', + footerCells : '', + even : 'ui-widget-content', // even row zebra striping + odd : 'ui-state-default' // odd row zebra striping + } + }; + + $.extend(ts.css, { + wrapper : 'tablesorter-wrapper' // ui theme & resizable + }); + + ts.addWidget({ + id: 'uitheme', + priority: 10, + format: function(table, c, wo) { + var i, hdr, icon, time, $header, $icon, $tfoot, $h, oldtheme, oldremove, oldIconRmv, hasOldTheme, + themesAll = ts.themes, + $table = c.$table.add( $( c.namespace + '_extra_table' ) ), + $headers = c.$headers.add( $( c.namespace + '_extra_headers' ) ), + theme = c.theme || 'jui', + themes = themesAll[theme] || {}, + remove = $.trim( [ themes.sortNone, themes.sortDesc, themes.sortAsc, themes.active ].join( ' ' ) ), + iconRmv = $.trim( [ themes.iconSortNone, themes.iconSortDesc, themes.iconSortAsc ].join( ' ' ) ); + if (c.debug) { time = new Date(); } + // initialization code - run once + if (!$table.hasClass('tablesorter-' + theme) || c.theme !== c.appliedTheme || !wo.uitheme_applied) { + wo.uitheme_applied = true; + oldtheme = themesAll[c.appliedTheme] || {}; + hasOldTheme = !$.isEmptyObject(oldtheme); + oldremove = hasOldTheme ? [ oldtheme.sortNone, oldtheme.sortDesc, oldtheme.sortAsc, oldtheme.active ].join( ' ' ) : ''; + oldIconRmv = hasOldTheme ? [ oldtheme.iconSortNone, oldtheme.iconSortDesc, oldtheme.iconSortAsc ].join( ' ' ) : ''; + if (hasOldTheme) { + wo.zebra[0] = $.trim( ' ' + wo.zebra[0].replace(' ' + oldtheme.even, '') ); + wo.zebra[1] = $.trim( ' ' + wo.zebra[1].replace(' ' + oldtheme.odd, '') ); + c.$tbodies.children().removeClass( [ oldtheme.even, oldtheme.odd ].join(' ') ); + } + // update zebra stripes + if (themes.even) { wo.zebra[0] += ' ' + themes.even; } + if (themes.odd) { wo.zebra[1] += ' ' + themes.odd; } + // add caption style + $table.children('caption') + .removeClass(oldtheme.caption || '') + .addClass(themes.caption); + // add table/footer class names + $tfoot = $table + // remove other selected themes + .removeClass( (c.appliedTheme ? 'tablesorter-' + (c.appliedTheme || '') : '') + ' ' + (oldtheme.table || '') ) + .addClass('tablesorter-' + theme + ' ' + (themes.table || '')) // add theme widget class name + .children('tfoot'); + c.appliedTheme = c.theme; + + if ($tfoot.length) { + $tfoot + // if oldtheme.footerRow or oldtheme.footerCells are undefined, all class names are removed + .children('tr').removeClass(oldtheme.footerRow || '').addClass(themes.footerRow) + .children('th, td').removeClass(oldtheme.footerCells || '').addClass(themes.footerCells); + } + // update header classes + $headers + .removeClass( (hasOldTheme ? [ oldtheme.header, oldtheme.hover, oldremove ].join(' ') : '') || '' ) + .addClass(themes.header) + .not('.sorter-false') + .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') + .bind('mouseenter.tsuitheme mouseleave.tsuitheme', function(event) { + // toggleClass with switch added in jQuery 1.3 + $(this)[ event.type === 'mouseenter' ? 'addClass' : 'removeClass' ](themes.hover || ''); + }); + + $headers.each(function(){ + var $this = $(this); + if (!$this.find('.' + ts.css.wrapper).length) { + // Firefox needs this inner div to position the icon & resizer correctly + $this.wrapInner('
'); + } + }); + if (c.cssIcon) { + // if c.cssIcon is '', then no is added to the header + $headers + .find('.' + ts.css.icon) + .removeClass(hasOldTheme ? [ oldtheme.icons, oldIconRmv ].join(' ') : '') + .addClass(themes.icons || ''); + } + if ($table.hasClass('hasFilters')) { + $table.children('thead').children('.' + ts.css.filterRow) + .removeClass(hasOldTheme ? oldtheme.filterRow || '' : '') + .addClass(themes.filterRow || ''); + } + } + for (i = 0; i < c.columns; i++) { + $header = c.$headers + .add($(c.namespace + '_extra_headers')) + .not('.sorter-false') + .filter('[data-column="' + i + '"]'); + $icon = (ts.css.icon) ? $header.find('.' + ts.css.icon) : $(); + $h = $headers.not('.sorter-false').filter('[data-column="' + i + '"]:last'); + if ($h.length) { + $header.removeClass(remove); + $icon.removeClass(iconRmv); + if ($h[0].sortDisabled) { + // no sort arrows for disabled columns! + $icon.removeClass(themes.icons || ''); + } else { + hdr = themes.sortNone; + icon = themes.iconSortNone; + if ($h.hasClass(ts.css.sortAsc)) { + hdr = [ themes.sortAsc, themes.active ].join(' '); + icon = themes.iconSortAsc; + } else if ($h.hasClass(ts.css.sortDesc)) { + hdr = [ themes.sortDesc, themes.active ].join(' '); + icon = themes.iconSortDesc; + } + $header.addClass(hdr); + $icon.addClass(icon || ''); + } + } + } + if (c.debug) { + console.log('Applying ' + theme + ' theme' + ts.benchmark(time)); + } + }, + remove: function(table, c, wo, refreshing) { + if (!wo.uitheme_applied) { return; } + var $table = c.$table, + theme = c.appliedTheme || 'jui', + themes = ts.themes[ theme ] || ts.themes.jui, + $headers = $table.children('thead').children(), + remove = themes.sortNone + ' ' + themes.sortDesc + ' ' + themes.sortAsc, + iconRmv = themes.iconSortNone + ' ' + themes.iconSortDesc + ' ' + themes.iconSortAsc; + $table.removeClass('tablesorter-' + theme + ' ' + themes.table); + wo.uitheme_applied = false; + if (refreshing) { return; } + $table.find(ts.css.header).removeClass(themes.header); + $headers + .unbind('mouseenter.tsuitheme mouseleave.tsuitheme') // remove hover + .removeClass(themes.hover + ' ' + remove + ' ' + themes.active) + .filter('.' + ts.css.filterRow) + .removeClass(themes.filterRow); + $headers.find('.' + ts.css.icon).removeClass(themes.icons + ' ' + iconRmv); + } + }); + +})(jQuery); + +/*! Widget: columns */ +;(function ($) { + 'use strict'; + var ts = $.tablesorter || {}; + + ts.addWidget({ + id: 'columns', + priority: 30, + options : { + columns : [ 'primary', 'secondary', 'tertiary' ] + }, + format: function(table, c, wo) { + var $tbody, tbodyIndex, $rows, rows, $row, $cells, remove, indx, + $table = c.$table, + $tbodies = c.$tbodies, + sortList = c.sortList, + len = sortList.length, + // removed c.widgetColumns support + css = wo && wo.columns || [ 'primary', 'secondary', 'tertiary' ], + last = css.length - 1; + remove = css.join(' '); + // check if there is a sort (on initialization there may not be one) + for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // detach tbody + $rows = $tbody.children('tr'); + // loop through the visible rows + $rows.each(function() { + $row = $(this); + if (this.style.display !== 'none') { + // remove all columns class names + $cells = $row.children().removeClass(remove); + // add appropriate column class names + if (sortList && sortList[0]) { + // primary sort column class + $cells.eq(sortList[0][0]).addClass(css[0]); + if (len > 1) { + for (indx = 1; indx < len; indx++) { + // secondary, tertiary, etc sort column classes + $cells.eq(sortList[indx][0]).addClass( css[indx] || css[last] ); + } + } + } + } + }); + ts.processTbody(table, $tbody, false); + } + // add classes to thead and tfoot + rows = wo.columns_thead !== false ? [ 'thead tr' ] : []; + if (wo.columns_tfoot !== false) { + rows.push('tfoot tr'); + } + if (rows.length) { + $rows = $table.find( rows.join(',') ).children().removeClass(remove); + if (len) { + for (indx = 0; indx < len; indx++) { + // add primary. secondary, tertiary, etc sort column classes + $rows.filter('[data-column="' + sortList[indx][0] + '"]').addClass(css[indx] || css[last]); + } + } + } + }, + remove: function(table, c, wo) { + var tbodyIndex, $tbody, + $tbodies = c.$tbodies, + remove = (wo.columns || [ 'primary', 'secondary', 'tertiary' ]).join(' '); + c.$headers.removeClass(remove); + c.$table.children('tfoot').children('tr').children('th, td').removeClass(remove); + for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody + $tbody.children('tr').each(function() { + $(this).children().removeClass(remove); + }); + ts.processTbody(table, $tbody, false); // restore tbody + } + } + }); + +})(jQuery); + +/*! Widget: filter - updated 3/18/2016 (v2.25.6) *//* + * Requires tablesorter v2.8+ and jQuery 1.7+ + * by Rob Garrison + */ +;( function ( $ ) { + 'use strict'; + var tsf, tsfRegex, + ts = $.tablesorter || {}, + tscss = ts.css, + tskeyCodes = ts.keyCodes; + + $.extend( tscss, { + filterRow : 'tablesorter-filter-row', + filter : 'tablesorter-filter', + filterDisabled : 'disabled', + filterRowHide : 'hideme' + }); + + $.extend( tskeyCodes, { + backSpace : 8, + escape : 27, + space : 32, + left : 37, + down : 40 + }); + + ts.addWidget({ + id: 'filter', + priority: 50, + options : { + filter_childRows : false, // if true, filter includes child row content in the search + filter_childByColumn : false, // ( filter_childRows must be true ) if true = search child rows by column; false = search all child row text grouped + filter_childWithSibs : true, // if true, include matching child row siblings + filter_columnFilters : true, // if true, a filter will be added to the top of each table column + filter_columnAnyMatch: true, // if true, allows using '#:{query}' in AnyMatch searches ( column:query ) + filter_cellFilter : '', // css class name added to the filter cell ( string or array ) + filter_cssFilter : '', // css class name added to the filter row & each input in the row ( tablesorter-filter is ALWAYS added ) + filter_defaultFilter : {}, // add a default column filter type '~{query}' to make fuzzy searches default; '{q1} AND {q2}' to make all searches use a logical AND. + filter_excludeFilter : {}, // filters to exclude, per column + filter_external : '', // jQuery selector string ( or jQuery object ) of external filters + filter_filteredRow : 'filtered', // class added to filtered rows; define in css with "display:none" to hide the filtered-out rows + filter_formatter : null, // add custom filter elements to the filter row + filter_functions : null, // add custom filter functions using this option + filter_hideEmpty : true, // hide filter row when table is empty + filter_hideFilters : false, // collapse filter row when mouse leaves the area + filter_ignoreCase : true, // if true, make all searches case-insensitive + filter_liveSearch : true, // if true, search column content while the user types ( with a delay ) + filter_matchType : { 'input': 'exact', 'select': 'exact' }, // global query settings ('exact' or 'match'); overridden by "filter-match" or "filter-exact" class + filter_onlyAvail : 'filter-onlyAvail', // a header with a select dropdown & this class name will only show available ( visible ) options within the drop down + filter_placeholder : { search : '', select : '' }, // default placeholder text ( overridden by any header 'data-placeholder' setting ) + filter_reset : null, // jQuery selector string of an element used to reset the filters + filter_resetOnEsc : true, // Reset filter input when the user presses escape - normalized across browsers + filter_saveFilters : false, // Use the $.tablesorter.storage utility to save the most recent filters + filter_searchDelay : 300, // typing delay in milliseconds before starting a search + filter_searchFiltered: true, // allow searching through already filtered rows in special circumstances; will speed up searching in large tables if true + filter_selectSource : null, // include a function to return an array of values to be added to the column filter select + filter_startsWith : false, // if true, filter start from the beginning of the cell contents + filter_useParsedData : false, // filter all data using parsed content + filter_serversideFiltering : false, // if true, must perform server-side filtering b/c client-side filtering is disabled, but the ui and events will still be used. + filter_defaultAttrib : 'data-value', // data attribute in the header cell that contains the default filter value + filter_selectSourceSeparator : '|' // filter_selectSource array text left of the separator is added to the option value, right into the option text + }, + format: function( table, c, wo ) { + if ( !c.$table.hasClass( 'hasFilters' ) ) { + tsf.init( table, c, wo ); + } + }, + remove: function( table, c, wo, refreshing ) { + var tbodyIndex, $tbody, + $table = c.$table, + $tbodies = c.$tbodies, + events = 'addRows updateCell update updateRows updateComplete appendCache filterReset filterEnd search ' + .split( ' ' ).join( c.namespace + 'filter ' ); + $table + .removeClass( 'hasFilters' ) + // add filter namespace to all BUT search + .unbind( events.replace( ts.regex.spaces, ' ' ) ) + // remove the filter row even if refreshing, because the column might have been moved + .find( '.' + tscss.filterRow ).remove(); + if ( refreshing ) { return; } + for ( tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody( table, $tbodies.eq( tbodyIndex ), true ); // remove tbody + $tbody.children().removeClass( wo.filter_filteredRow ).show(); + ts.processTbody( table, $tbody, false ); // restore tbody + } + if ( wo.filter_reset ) { + $( document ).undelegate( wo.filter_reset, 'click' + c.namespace + 'filter' ); + } + } + }); + + tsf = ts.filter = { + + // regex used in filter 'check' functions - not for general use and not documented + regex: { + regex : /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})?$/, // regex to test for regex + child : /tablesorter-childRow/, // child row class name; this gets updated in the script + filtered : /filtered/, // filtered (hidden) row class name; updated in the script + type : /undefined|number/, // check type + exact : /(^[\"\'=]+)|([\"\'=]+$)/g, // exact match (allow '==') + operators : /[<>=]/g, // replace operators + query : '(q|query)', // replace filter queries + wild01 : /\?/g, // wild card match 0 or 1 + wild0More : /\*/g, // wild care match 0 or more + quote : /\"/g, + isNeg1 : /(>=?\s*-\d)/, + isNeg2 : /(<=?\s*\d)/ + }, + // function( c, data ) { } + // c = table.config + // data.$row = jQuery object of the row currently being processed + // data.$cells = jQuery object of all cells within the current row + // data.filters = array of filters for all columns ( some may be undefined ) + // data.filter = filter for the current column + // data.iFilter = same as data.filter, except lowercase ( if wo.filter_ignoreCase is true ) + // data.exact = table cell text ( or parsed data if column parser enabled; may be a number & not a string ) + // data.iExact = same as data.exact, except lowercase ( if wo.filter_ignoreCase is true; may be a number & not a string ) + // data.cache = table cell text from cache, so it has been parsed ( & in all lower case if c.ignoreCase is true ) + // data.cacheArray = An array of parsed content from each table cell in the row being processed + // data.index = column index; table = table element ( DOM ) + // data.parsed = array ( by column ) of boolean values ( from filter_useParsedData or 'filter-parsed' class ) + types: { + or : function( c, data, vars ) { + // look for "|", but not if it is inside of a regular expression + if ( ( tsfRegex.orTest.test( data.iFilter ) || tsfRegex.orSplit.test( data.filter ) ) && + // this test for regex has potential to slow down the overall search + !tsfRegex.regex.test( data.filter ) ) { + var indx, filterMatched, query, regex, + // duplicate data but split filter + data2 = $.extend( {}, data ), + filter = data.filter.split( tsfRegex.orSplit ), + iFilter = data.iFilter.split( tsfRegex.orSplit ), + len = filter.length; + for ( indx = 0; indx < len; indx++ ) { + data2.nestedFilters = true; + data2.filter = '' + ( tsf.parseFilter( c, filter[ indx ], data ) || '' ); + data2.iFilter = '' + ( tsf.parseFilter( c, iFilter[ indx ], data ) || '' ); + query = '(' + ( tsf.parseFilter( c, data2.filter, data ) || '' ) + ')'; + try { + // use try/catch, because query may not be a valid regex if "|" is contained within a partial regex search, + // e.g "/(Alex|Aar" -> Uncaught SyntaxError: Invalid regular expression: /(/(Alex)/: Unterminated group + regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); + // filterMatched = data2.filter === '' && indx > 0 ? true + // look for an exact match with the 'or' unless the 'filter-match' class is found + filterMatched = regex.test( data2.exact ) || tsf.processTypes( c, data2, vars ); + if ( filterMatched ) { + return filterMatched; + } + } catch ( error ) { + return null; + } + } + // may be null from processing types + return filterMatched || false; + } + return null; + }, + // Look for an AND or && operator ( logical and ) + and : function( c, data, vars ) { + if ( tsfRegex.andTest.test( data.filter ) ) { + var indx, filterMatched, result, query, regex, + // duplicate data but split filter + data2 = $.extend( {}, data ), + filter = data.filter.split( tsfRegex.andSplit ), + iFilter = data.iFilter.split( tsfRegex.andSplit ), + len = filter.length; + for ( indx = 0; indx < len; indx++ ) { + data2.nestedFilters = true; + data2.filter = '' + ( tsf.parseFilter( c, filter[ indx ], data ) || '' ); + data2.iFilter = '' + ( tsf.parseFilter( c, iFilter[ indx ], data ) || '' ); + query = ( '(' + ( tsf.parseFilter( c, data2.filter, data ) || '' ) + ')' ) + // replace wild cards since /(a*)/i will match anything + .replace( tsfRegex.wild01, '\\S{1}' ).replace( tsfRegex.wild0More, '\\S*' ); + try { + // use try/catch just in case RegExp is invalid + regex = new RegExp( data.isMatch ? query : '^' + query + '$', c.widgetOptions.filter_ignoreCase ? 'i' : '' ); + // look for an exact match with the 'and' unless the 'filter-match' class is found + result = ( regex.test( data2.exact ) || tsf.processTypes( c, data2, vars ) ); + if ( indx === 0 ) { + filterMatched = result; + } else { + filterMatched = filterMatched && result; + } + } catch ( error ) { + return null; + } + } + // may be null from processing types + return filterMatched || false; + } + return null; + }, + // Look for regex + regex: function( c, data ) { + if ( tsfRegex.regex.test( data.filter ) ) { + var matches, + // cache regex per column for optimal speed + regex = data.filter_regexCache[ data.index ] || tsfRegex.regex.exec( data.filter ), + isRegex = regex instanceof RegExp; + try { + if ( !isRegex ) { + // force case insensitive search if ignoreCase option set? + // if ( c.ignoreCase && !regex[2] ) { regex[2] = 'i'; } + data.filter_regexCache[ data.index ] = regex = new RegExp( regex[1], regex[2] ); + } + matches = regex.test( data.exact ); + } catch ( error ) { + matches = false; + } + return matches; + } + return null; + }, + // Look for operators >, >=, < or <= + operators: function( c, data ) { + // ignore empty strings... because '' < 10 is true + if ( tsfRegex.operTest.test( data.iFilter ) && data.iExact !== '' ) { + var cachedValue, result, txt, + table = c.table, + parsed = data.parsed[ data.index ], + query = ts.formatFloat( data.iFilter.replace( tsfRegex.operators, '' ), table ), + parser = c.parsers[ data.index ] || {}, + savedSearch = query; + // parse filter value in case we're comparing numbers ( dates ) + if ( parsed || parser.type === 'numeric' ) { + txt = $.trim( '' + data.iFilter.replace( tsfRegex.operators, '' ) ); + result = tsf.parseFilter( c, txt, data, true ); + query = ( typeof result === 'number' && result !== '' && !isNaN( result ) ) ? result : query; + } + // iExact may be numeric - see issue #149; + // check if cached is defined, because sometimes j goes out of range? ( numeric columns ) + if ( ( parsed || parser.type === 'numeric' ) && !isNaN( query ) && + typeof data.cache !== 'undefined' ) { + cachedValue = data.cache; + } else { + txt = isNaN( data.iExact ) ? data.iExact.replace( ts.regex.nondigit, '' ) : data.iExact; + cachedValue = ts.formatFloat( txt, table ); + } + if ( tsfRegex.gtTest.test( data.iFilter ) ) { + result = tsfRegex.gteTest.test( data.iFilter ) ? cachedValue >= query : cachedValue > query; + } else if ( tsfRegex.ltTest.test( data.iFilter ) ) { + result = tsfRegex.lteTest.test( data.iFilter ) ? cachedValue <= query : cachedValue < query; + } + // keep showing all rows if nothing follows the operator + if ( !result && savedSearch === '' ) { + result = true; + } + return result; + } + return null; + }, + // Look for a not match + notMatch: function( c, data ) { + if ( tsfRegex.notTest.test( data.iFilter ) ) { + var indx, + txt = data.iFilter.replace( '!', '' ), + filter = tsf.parseFilter( c, txt, data ) || ''; + if ( tsfRegex.exact.test( filter ) ) { + // look for exact not matches - see #628 + filter = filter.replace( tsfRegex.exact, '' ); + return filter === '' ? true : $.trim( filter ) !== data.iExact; + } else { + indx = data.iExact.search( $.trim( filter ) ); + return filter === '' ? true : !( c.widgetOptions.filter_startsWith ? indx === 0 : indx >= 0 ); + } + } + return null; + }, + // Look for quotes or equals to get an exact match; ignore type since iExact could be numeric + exact: function( c, data ) { + /*jshint eqeqeq:false */ + if ( tsfRegex.exact.test( data.iFilter ) ) { + var txt = data.iFilter.replace( tsfRegex.exact, '' ), + filter = tsf.parseFilter( c, txt, data ) || ''; + return data.anyMatch ? $.inArray( filter, data.rowArray ) >= 0 : filter == data.iExact; + } + return null; + }, + // Look for a range ( using ' to ' or ' - ' ) - see issue #166; thanks matzhu! + range : function( c, data ) { + if ( tsfRegex.toTest.test( data.iFilter ) ) { + var result, tmp, range1, range2, + table = c.table, + index = data.index, + parsed = data.parsed[index], + // make sure the dash is for a range and not indicating a negative number + query = data.iFilter.split( tsfRegex.toSplit ); + + tmp = query[0].replace( ts.regex.nondigit, '' ) || ''; + range1 = ts.formatFloat( tsf.parseFilter( c, tmp, data ), table ); + tmp = query[1].replace( ts.regex.nondigit, '' ) || ''; + range2 = ts.formatFloat( tsf.parseFilter( c, tmp, data ), table ); + // parse filter value in case we're comparing numbers ( dates ) + if ( parsed || c.parsers[ index ].type === 'numeric' ) { + result = c.parsers[ index ].format( '' + query[0], table, c.$headers.eq( index ), index ); + range1 = ( result !== '' && !isNaN( result ) ) ? result : range1; + result = c.parsers[ index ].format( '' + query[1], table, c.$headers.eq( index ), index ); + range2 = ( result !== '' && !isNaN( result ) ) ? result : range2; + } + if ( ( parsed || c.parsers[ index ].type === 'numeric' ) && !isNaN( range1 ) && !isNaN( range2 ) ) { + result = data.cache; + } else { + tmp = isNaN( data.iExact ) ? data.iExact.replace( ts.regex.nondigit, '' ) : data.iExact; + result = ts.formatFloat( tmp, table ); + } + if ( range1 > range2 ) { + tmp = range1; range1 = range2; range2 = tmp; // swap + } + return ( result >= range1 && result <= range2 ) || ( range1 === '' || range2 === '' ); + } + return null; + }, + // Look for wild card: ? = single, * = multiple, or | = logical OR + wild : function( c, data ) { + if ( tsfRegex.wildOrTest.test( data.iFilter ) ) { + var query = '' + ( tsf.parseFilter( c, data.iFilter, data ) || '' ); + // look for an exact match with the 'or' unless the 'filter-match' class is found + if ( !tsfRegex.wildTest.test( query ) && data.nestedFilters ) { + query = data.isMatch ? query : '^(' + query + ')$'; + } + // parsing the filter may not work properly when using wildcards =/ + try { + return new RegExp( + query.replace( tsfRegex.wild01, '\\S{1}' ).replace( tsfRegex.wild0More, '\\S*' ), + c.widgetOptions.filter_ignoreCase ? 'i' : '' + ) + .test( data.exact ); + } catch ( error ) { + return null; + } + } + return null; + }, + // fuzzy text search; modified from https://github.com/mattyork/fuzzy ( MIT license ) + fuzzy: function( c, data ) { + if ( tsfRegex.fuzzyTest.test( data.iFilter ) ) { + var indx, + patternIndx = 0, + len = data.iExact.length, + txt = data.iFilter.slice( 1 ), + pattern = tsf.parseFilter( c, txt, data ) || ''; + for ( indx = 0; indx < len; indx++ ) { + if ( data.iExact[ indx ] === pattern[ patternIndx ] ) { + patternIndx += 1; + } + } + return patternIndx === pattern.length; + } + return null; + } + }, + init: function( table, c, wo ) { + // filter language options + ts.language = $.extend( true, {}, { + to : 'to', + or : 'or', + and : 'and' + }, ts.language ); + + var options, string, txt, $header, column, filters, val, fxn, noSelect; + c.$table.addClass( 'hasFilters' ); + c.lastSearch = []; + + // define timers so using clearTimeout won't cause an undefined error + wo.filter_searchTimer = null; + wo.filter_initTimer = null; + wo.filter_formatterCount = 0; + wo.filter_formatterInit = []; + wo.filter_anyColumnSelector = '[data-column="all"],[data-column="any"]'; + wo.filter_multipleColumnSelector = '[data-column*="-"],[data-column*=","]'; + + val = '\\{' + tsfRegex.query + '\\}'; + $.extend( tsfRegex, { + child : new RegExp( c.cssChildRow ), + filtered : new RegExp( wo.filter_filteredRow ), + alreadyFiltered : new RegExp( '(\\s+(' + ts.language.or + '|-|' + ts.language.to + ')\\s+)', 'i' ), + toTest : new RegExp( '\\s+(-|' + ts.language.to + ')\\s+', 'i' ), + toSplit : new RegExp( '(?:\\s+(?:-|' + ts.language.to + ')\\s+)', 'gi' ), + andTest : new RegExp( '\\s+(' + ts.language.and + '|&&)\\s+', 'i' ), + andSplit : new RegExp( '(?:\\s+(?:' + ts.language.and + '|&&)\\s+)', 'gi' ), + orTest : new RegExp( '(\\||\\s+' + ts.language.or + '\\s+)', 'i' ), + orSplit : new RegExp( '(?:\\s+(?:' + ts.language.or + ')\\s+|\\|)', 'gi' ), + iQuery : new RegExp( val, 'i' ), + igQuery : new RegExp( val, 'ig' ), + operTest : /^[<>]=?/, + gtTest : />/, + gteTest : />=/, + ltTest : /' + + ( $header.data( 'placeholder' ) || + $header.attr( 'data-placeholder' ) || + wo.filter_placeholder.select || + '' + ) + + '' : ''; + val = string; + txt = string; + if ( string.indexOf( wo.filter_selectSourceSeparator ) >= 0 ) { + val = string.split( wo.filter_selectSourceSeparator ); + txt = val[1]; + val = val[0]; + } + options += ''; + } + } + c.$table + .find( 'thead' ) + .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) + .append( options ); + txt = wo.filter_selectSource; + fxn = typeof txt === 'function' ? true : ts.getColumnData( table, txt, column ); + if ( fxn ) { + // updating so the extra options are appended + tsf.buildSelect( c.table, column, '', true, $header.hasClass( wo.filter_onlyAvail ) ); + } + } + } + } + } + // not really updating, but if the column has both the 'filter-select' class & + // filter_functions set to true, it would append the same options twice. + tsf.buildDefault( table, true ); + + tsf.bindSearch( table, c.$table.find( '.' + tscss.filter ), true ); + if ( wo.filter_external ) { + tsf.bindSearch( table, wo.filter_external ); + } + + if ( wo.filter_hideFilters ) { + tsf.hideFilters( c ); + } + + // show processing icon + if ( c.showProcessing ) { + txt = 'filterStart filterEnd '.split( ' ' ).join( c.namespace + 'filter ' ); + c.$table + .unbind( txt.replace( ts.regex.spaces, ' ' ) ) + .bind( txt, function( event, columns ) { + // only add processing to certain columns to all columns + $header = ( columns ) ? + c.$table + .find( '.' + tscss.header ) + .filter( '[data-column]' ) + .filter( function() { + return columns[ $( this ).data( 'column' ) ] !== ''; + }) : ''; + ts.isProcessing( table, event.type === 'filterStart', columns ? $header : '' ); + }); + } + + // set filtered rows count ( intially unfiltered ) + c.filteredRows = c.totalRows; + + // add default values + txt = 'tablesorter-initialized pagerBeforeInitialized '.split( ' ' ).join( c.namespace + 'filter ' ); + c.$table + .unbind( txt.replace( ts.regex.spaces, ' ' ) ) + .bind( txt, function() { + // redefine 'wo' as it does not update properly inside this callback + var wo = this.config.widgetOptions; + filters = tsf.setDefaults( table, c, wo ) || []; + if ( filters.length ) { + // prevent delayInit from triggering a cache build if filters are empty + if ( !( c.delayInit && filters.join( '' ) === '' ) ) { + ts.setFilters( table, filters, true ); + } + } + c.$table.triggerHandler( 'filterFomatterUpdate' ); + // trigger init after setTimeout to prevent multiple filterStart/End/Init triggers + setTimeout( function() { + if ( !wo.filter_initialized ) { + tsf.filterInitComplete( c ); + } + }, 100 ); + }); + // if filter widget is added after pager has initialized; then set filter init flag + if ( c.pager && c.pager.initialized && !wo.filter_initialized ) { + c.$table.triggerHandler( 'filterFomatterUpdate' ); + setTimeout( function() { + tsf.filterInitComplete( c ); + }, 100 ); + } + }, + // $cell parameter, but not the config, is passed to the filter_formatters, + // so we have to work with it instead + formatterUpdated: function( $cell, column ) { + // prevent error if $cell is undefined - see #1056 + var wo = $cell && $cell.closest( 'table' )[0].config.widgetOptions; + if ( wo && !wo.filter_initialized ) { + // add updates by column since this function + // may be called numerous times before initialization + wo.filter_formatterInit[ column ] = 1; + } + }, + filterInitComplete: function( c ) { + var indx, len, + wo = c.widgetOptions, + count = 0, + completed = function() { + wo.filter_initialized = true; + c.$table.triggerHandler( 'filterInit', c ); + tsf.findRows( c.table, c.$table.data( 'lastSearch' ) || [] ); + }; + if ( $.isEmptyObject( wo.filter_formatter ) ) { + completed(); + } else { + len = wo.filter_formatterInit.length; + for ( indx = 0; indx < len; indx++ ) { + if ( wo.filter_formatterInit[ indx ] === 1 ) { + count++; + } + } + clearTimeout( wo.filter_initTimer ); + if ( !wo.filter_initialized && count === wo.filter_formatterCount ) { + // filter widget initialized + completed(); + } else if ( !wo.filter_initialized ) { + // fall back in case a filter_formatter doesn't call + // $.tablesorter.filter.formatterUpdated( $cell, column ), and the count is off + wo.filter_initTimer = setTimeout( function() { + completed(); + }, 500 ); + } + } + }, + // encode or decode filters for storage; see #1026 + processFilters: function( filters, encode ) { + var indx, + mode = encode ? encodeURIComponent : decodeURIComponent, + len = filters.length; + for ( indx = 0; indx < len; indx++ ) { + if ( filters[ indx ] ) { + filters[ indx ] = mode( filters[ indx ] ); + } + } + return filters; + }, + setDefaults: function( table, c, wo ) { + var isArray, saved, indx, col, $filters, + // get current ( default ) filters + filters = ts.getFilters( table ) || []; + if ( wo.filter_saveFilters && ts.storage ) { + saved = ts.storage( table, 'tablesorter-filters' ) || []; + isArray = $.isArray( saved ); + // make sure we're not just getting an empty array + if ( !( isArray && saved.join( '' ) === '' || !isArray ) ) { + filters = tsf.processFilters( saved ); + } + } + // if no filters saved, then check default settings + if ( filters.join( '' ) === '' ) { + // allow adding default setting to external filters + $filters = c.$headers.add( wo.filter_$externalFilters ) + .filter( '[' + wo.filter_defaultAttrib + ']' ); + for ( indx = 0; indx <= c.columns; indx++ ) { + // include data-column='all' external filters + col = indx === c.columns ? 'all' : indx; + filters[ indx ] = $filters + .filter( '[data-column="' + col + '"]' ) + .attr( wo.filter_defaultAttrib ) || filters[indx] || ''; + } + } + c.$table.data( 'lastSearch', filters ); + return filters; + }, + parseFilter: function( c, filter, data, parsed ) { + return parsed || data.parsed[ data.index ] ? + c.parsers[ data.index ].format( filter, c.table, [], data.index ) : + filter; + }, + buildRow: function( table, c, wo ) { + var $filter, col, column, $header, makeSelect, disabled, name, ffxn, tmp, + // c.columns defined in computeThIndexes() + cellFilter = wo.filter_cellFilter, + columns = c.columns, + arry = $.isArray( cellFilter ), + buildFilter = ''; + for ( column = 0; column < columns; column++ ) { + if ( c.$headerIndexed[ column ].length ) { + // account for entire column set with colspan. See #1047 + tmp = c.$headerIndexed[ column ] && c.$headerIndexed[ column ][0].colSpan || 0; + if ( tmp > 1 ) { + buildFilter += '' ).appendTo( $filter ); + } else { + ffxn = ts.getColumnData( table, wo.filter_formatter, column ); + if ( ffxn ) { + wo.filter_formatterCount++; + buildFilter = ffxn( $filter, column ); + // no element returned, so lets go find it + if ( buildFilter && buildFilter.length === 0 ) { + buildFilter = $filter.children( 'input' ); + } + // element not in DOM, so lets attach it + if ( buildFilter && ( buildFilter.parent().length === 0 || + ( buildFilter.parent().length && buildFilter.parent()[0] !== $filter[0] ) ) ) { + $filter.append( buildFilter ); + } + } else { + buildFilter = $( '' ).appendTo( $filter ); + } + if ( buildFilter ) { + tmp = $header.data( 'placeholder' ) || + $header.attr( 'data-placeholder' ) || + wo.filter_placeholder.search || ''; + buildFilter.attr( 'placeholder', tmp ); + } + } + if ( buildFilter ) { + // add filter class name + name = ( $.isArray( wo.filter_cssFilter ) ? + ( typeof wo.filter_cssFilter[column] !== 'undefined' ? wo.filter_cssFilter[column] || '' : '' ) : + wo.filter_cssFilter ) || ''; + // copy data-column from table cell (it will include colspan) + buildFilter.addClass( tscss.filter + ' ' + name ).attr( 'data-column', $filter.attr( 'data-column' ) ); + if ( disabled ) { + buildFilter.attr( 'placeholder', '' ).addClass( tscss.filterDisabled )[0].disabled = true; + } + } + } + } + }, + bindSearch: function( table, $el, internal ) { + table = $( table )[0]; + $el = $( $el ); // allow passing a selector string + if ( !$el.length ) { return; } + var tmp, + c = table.config, + wo = c.widgetOptions, + namespace = c.namespace + 'filter', + $ext = wo.filter_$externalFilters; + if ( internal !== true ) { + // save anyMatch element + tmp = wo.filter_anyColumnSelector + ',' + wo.filter_multipleColumnSelector; + wo.filter_$anyMatch = $el.filter( tmp ); + if ( $ext && $ext.length ) { + wo.filter_$externalFilters = wo.filter_$externalFilters.add( $el ); + } else { + wo.filter_$externalFilters = $el; + } + // update values ( external filters added after table initialization ) + ts.setFilters( table, c.$table.data( 'lastSearch' ) || [], internal === false ); + } + // unbind events + tmp = ( 'keypress keyup keydown search change input '.split( ' ' ).join( namespace + ' ' ) ); + $el + // use data attribute instead of jQuery data since the head is cloned without including + // the data/binding + .attr( 'data-lastSearchTime', new Date().getTime() ) + .unbind( tmp.replace( ts.regex.spaces, ' ' ) ) + .bind( 'keydown' + namespace, function( event ) { + if ( event.which === tskeyCodes.escape && !wo.filter_resetOnEsc ) { + // prevent keypress event + return false; + } + }) + .bind( 'keyup' + namespace, function( event ) { + var column = parseInt( $( this ).attr( 'data-column' ), 10 ); + $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); + // emulate what webkit does.... escape clears the filter + if ( event.which === tskeyCodes.escape ) { + // make sure to restore the last value on escape + this.value = wo.filter_resetOnEsc ? '' : c.lastSearch[column]; + // live search + } else if ( wo.filter_liveSearch === false ) { + return; + // don't return if the search value is empty ( all rows need to be revealed ) + } else if ( this.value !== '' && ( + // liveSearch can contain a min value length; ignore arrow and meta keys, but allow backspace + ( typeof wo.filter_liveSearch === 'number' && this.value.length < wo.filter_liveSearch ) || + // let return & backspace continue on, but ignore arrows & non-valid characters + ( event.which !== tskeyCodes.enter && event.which !== tskeyCodes.backSpace && + ( event.which < tskeyCodes.space || ( event.which >= tskeyCodes.left && event.which <= tskeyCodes.down ) ) ) ) ) { + return; + } + // change event = no delay; last true flag tells getFilters to skip newest timed input + tsf.searching( table, true, true ); + }) + // include change for select - fixes #473 + .bind( 'search change keypress input '.split( ' ' ).join( namespace + ' ' ), function( event ) { + // don't get cached data, in case data-column changes dynamically + var column = parseInt( $( this ).attr( 'data-column' ), 10 ); + // don't allow 'change' event to process if the input value is the same - fixes #685 + if ( wo.filter_initialized && ( event.which === tskeyCodes.enter || event.type === 'search' || + ( event.type === 'change' ) && this.value !== c.lastSearch[column] ) || + // only "input" event fires in MS Edge when clicking the "x" to clear the search + ( event.type === 'input' && this.value === '' ) ) { + event.preventDefault(); + // init search with no delay + $( this ).attr( 'data-lastSearchTime', new Date().getTime() ); + tsf.searching( table, event.type !== 'keypress', true ); + } + }); + }, + searching: function( table, filter, skipFirst ) { + var wo = table.config.widgetOptions; + clearTimeout( wo.filter_searchTimer ); + if ( typeof filter === 'undefined' || filter === true ) { + // delay filtering + wo.filter_searchTimer = setTimeout( function() { + tsf.checkFilters( table, filter, skipFirst ); + }, wo.filter_liveSearch ? wo.filter_searchDelay : 10 ); + } else { + // skip delay + tsf.checkFilters( table, filter, skipFirst ); + } + }, + checkFilters: function( table, filter, skipFirst ) { + var c = table.config, + wo = c.widgetOptions, + filterArray = $.isArray( filter ), + filters = ( filterArray ) ? filter : ts.getFilters( table, true ), + combinedFilters = ( filters || [] ).join( '' ); // combined filter values + // prevent errors if delay init is set + if ( $.isEmptyObject( c.cache ) ) { + // update cache if delayInit set & pager has initialized ( after user initiates a search ) + if ( c.delayInit && ( !c.pager || c.pager && c.pager.initialized ) ) { + ts.updateCache( c, function() { + tsf.checkFilters( table, false, skipFirst ); + }); + } + return; + } + // add filter array back into inputs + if ( filterArray ) { + ts.setFilters( table, filters, false, skipFirst !== true ); + if ( !wo.filter_initialized ) { c.lastCombinedFilter = ''; } + } + if ( wo.filter_hideFilters ) { + // show/hide filter row as needed + c.$table + .find( '.' + tscss.filterRow ) + .triggerHandler( combinedFilters === '' ? 'mouseleave' : 'mouseenter' ); + } + // return if the last search is the same; but filter === false when updating the search + // see example-widget-filter.html filter toggle buttons + if ( c.lastCombinedFilter === combinedFilters && filter !== false ) { + return; + } else if ( filter === false ) { + // force filter refresh + c.lastCombinedFilter = null; + c.lastSearch = []; + } + // define filter inside it is false + filters = filters || []; + // convert filters to strings - see #1070 + filters = Array.prototype.map ? + filters.map( String ) : + // for IE8 & older browsers - maybe not the best method + filters.join( '\ufffd' ).split( '\ufffd' ); + + if ( wo.filter_initialized ) { + c.$table.triggerHandler( 'filterStart', [ filters ] ); + } + if ( c.showProcessing ) { + // give it time for the processing icon to kick in + setTimeout( function() { + tsf.findRows( table, filters, combinedFilters ); + return false; + }, 30 ); + } else { + tsf.findRows( table, filters, combinedFilters ); + return false; + } + }, + hideFilters: function( c, $table ) { + var timer, + $row = ( $table || c.$table ).find( '.' + tscss.filterRow ).addClass( tscss.filterRowHide ); + $row + .bind( 'mouseenter mouseleave', function( e ) { + // save event object - http://bugs.jquery.com/ticket/12140 + var event = e, + $filterRow = $( this ); + clearTimeout( timer ); + timer = setTimeout( function() { + if ( /enter|over/.test( event.type ) ) { + $filterRow.removeClass( tscss.filterRowHide ); + } else { + // don't hide if input has focus + // $( ':focus' ) needs jQuery 1.6+ + if ( $( document.activeElement ).closest( 'tr' )[0] !== $filterRow[0] ) { + // don't hide row if any filter has a value + if ( c.lastCombinedFilter === '' ) { + $filterRow.addClass( tscss.filterRowHide ); + } + } + } + }, 200 ); + }) + .find( 'input, select' ).bind( 'focus blur', function( e ) { + var event = e, + $row = $( this ).closest( 'tr' ); + clearTimeout( timer ); + timer = setTimeout( function() { + clearTimeout( timer ); + // don't hide row if any filter has a value + if ( ts.getFilters( c.$table ).join( '' ) === '' ) { + $row.toggleClass( tscss.filterRowHide, event.type !== 'focus' ); + } + }, 200 ); + }); + }, + defaultFilter: function( filter, mask ) { + if ( filter === '' ) { return filter; } + var regex = tsfRegex.iQuery, + maskLen = mask.match( tsfRegex.igQuery ).length, + query = maskLen > 1 ? $.trim( filter ).split( /\s/ ) : [ $.trim( filter ) ], + len = query.length - 1, + indx = 0, + val = mask; + if ( len < 1 && maskLen > 1 ) { + // only one 'word' in query but mask has >1 slots + query[1] = query[0]; + } + // replace all {query} with query words... + // if query = 'Bob', then convert mask from '!{query}' to '!Bob' + // if query = 'Bob Joe Frank', then convert mask '{q} OR {q}' to 'Bob OR Joe OR Frank' + while ( regex.test( val ) ) { + val = val.replace( regex, query[indx++] || '' ); + if ( regex.test( val ) && indx < len && ( query[indx] || '' ) !== '' ) { + val = mask.replace( regex, val ); + } + } + return val; + }, + getLatestSearch: function( $input ) { + if ( $input ) { + return $input.sort( function( a, b ) { + return $( b ).attr( 'data-lastSearchTime' ) - $( a ).attr( 'data-lastSearchTime' ); + }); + } + return $input || $(); + }, + findRange: function( c, val, ignoreRanges ) { + // look for multiple columns '1-3,4-6,8' in data-column + var temp, ranges, range, start, end, singles, i, indx, len, + columns = []; + if ( /^[0-9]+$/.test( val ) ) { + // always return an array + return [ parseInt( val, 10 ) ]; + } + // process column range + if ( !ignoreRanges && /-/.test( val ) ) { + ranges = val.match( /(\d+)\s*-\s*(\d+)/g ); + len = ranges ? ranges.length : 0; + for ( indx = 0; indx < len; indx++ ) { + range = ranges[indx].split( /\s*-\s*/ ); + start = parseInt( range[0], 10 ) || 0; + end = parseInt( range[1], 10 ) || ( c.columns - 1 ); + if ( start > end ) { + temp = start; start = end; end = temp; // swap + } + if ( end >= c.columns ) { + end = c.columns - 1; + } + for ( ; start <= end; start++ ) { + columns[ columns.length ] = start; + } + // remove processed range from val + val = val.replace( ranges[ indx ], '' ); + } + } + // process single columns + if ( !ignoreRanges && /,/.test( val ) ) { + singles = val.split( /\s*,\s*/ ); + len = singles.length; + for ( i = 0; i < len; i++ ) { + if ( singles[ i ] !== '' ) { + indx = parseInt( singles[ i ], 10 ); + if ( indx < c.columns ) { + columns[ columns.length ] = indx; + } + } + } + } + // return all columns + if ( !columns.length ) { + for ( indx = 0; indx < c.columns; indx++ ) { + columns[ columns.length ] = indx; + } + } + return columns; + }, + getColumnElm: function( c, $elements, column ) { + // data-column may contain multiple columns '1-3,5-6,8' + // replaces: c.$filters.filter( '[data-column="' + column + '"]' ); + return $elements.filter( function() { + var cols = tsf.findRange( c, $( this ).attr( 'data-column' ) ); + return $.inArray( column, cols ) > -1; + }); + }, + multipleColumns: function( c, $input ) { + // look for multiple columns '1-3,4-6,8' in data-column + var wo = c.widgetOptions, + // only target 'all' column inputs on initialization + // & don't target 'all' column inputs if they don't exist + targets = wo.filter_initialized || !$input.filter( wo.filter_anyColumnSelector ).length, + val = $.trim( tsf.getLatestSearch( $input ).attr( 'data-column' ) || '' ); + return tsf.findRange( c, val, !targets ); + }, + processTypes: function( c, data, vars ) { + var ffxn, + filterMatched = null, + matches = null; + for ( ffxn in tsf.types ) { + if ( $.inArray( ffxn, vars.excludeMatch ) < 0 && matches === null ) { + matches = tsf.types[ffxn]( c, data, vars ); + if ( matches !== null ) { + filterMatched = matches; + } + } + } + return filterMatched; + }, + matchType: function( c, columnIndex ) { + var isMatch, + $el = c.$headerIndexed[ columnIndex ]; + // filter-exact > filter-match > filter_matchType for type + if ( $el.hasClass( 'filter-exact' ) ) { + isMatch = false; + } else if ( $el.hasClass( 'filter-match' ) ) { + isMatch = true; + } else { + // filter-select is not applied when filter_functions are used, so look for a select + $el = c.$filters.eq( columnIndex ).find( '.' + tscss.filter ); + isMatch = $el.length ? + c.widgetOptions.filter_matchType[ ( $el[ 0 ].nodeName || '' ).toLowerCase() ] === 'match' : + // default to exact, if no inputs found + false; + } + return isMatch; + }, + processRow: function( c, data, vars ) { + var result, filterMatched, + fxn, ffxn, txt, + wo = c.widgetOptions, + showRow = true, + + // if wo.filter_$anyMatch data-column attribute is changed dynamically + // we don't want to do an "anyMatch" search on one column using data + // for the entire row - see #998 + columnIndex = wo.filter_$anyMatch && wo.filter_$anyMatch.length ? + // look for multiple columns '1-3,4-6,8' + tsf.multipleColumns( c, wo.filter_$anyMatch ) : + []; + + data.$cells = data.$row.children(); + + if ( data.anyMatchFlag && columnIndex.length > 1 ) { + data.anyMatch = true; + data.isMatch = true; + data.rowArray = data.$cells.map( function( i ) { + if ( $.inArray( i, columnIndex ) > -1 ) { + if ( data.parsed[ i ] ) { + txt = data.cacheArray[ i ]; + } else { + txt = data.rawArray[ i ]; + txt = $.trim( wo.filter_ignoreCase ? txt.toLowerCase() : txt ); + if ( c.sortLocaleCompare ) { + txt = ts.replaceAccents( txt ); + } + } + return txt; + } + }).get(); + data.filter = data.anyMatchFilter; + data.iFilter = data.iAnyMatchFilter; + data.exact = data.rowArray.join( ' ' ); + data.iExact = wo.filter_ignoreCase ? data.exact.toLowerCase() : data.exact; + data.cache = data.cacheArray.slice( 0, -1 ).join( ' ' ); + + vars.excludeMatch = vars.noAnyMatch; + filterMatched = tsf.processTypes( c, data, vars ); + if ( filterMatched !== null ) { + showRow = filterMatched; + } else { + if ( wo.filter_startsWith ) { + showRow = false; + // data.rowArray may not contain all columns + columnIndex = Math.min( c.columns, data.rowArray.length ); + while ( !showRow && columnIndex > 0 ) { + columnIndex--; + showRow = showRow || data.rowArray[ columnIndex ].indexOf( data.iFilter ) === 0; + } + } else { + showRow = ( data.iExact + data.childRowText ).indexOf( data.iFilter ) >= 0; + } + } + data.anyMatch = false; + // no other filters to process + if ( data.filters.join( '' ) === data.filter ) { + return showRow; + } + } + + for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { + data.filter = data.filters[ columnIndex ]; + data.index = columnIndex; + + // filter types to exclude, per column + vars.excludeMatch = vars.excludeFilter[ columnIndex ]; + + // ignore if filter is empty or disabled + if ( data.filter ) { + data.cache = data.cacheArray[ columnIndex ]; + result = data.parsed[ columnIndex ] ? data.cache : data.rawArray[ columnIndex ] || ''; + data.exact = c.sortLocaleCompare ? ts.replaceAccents( result ) : result; // issue #405 + data.iExact = !tsfRegex.type.test( typeof data.exact ) && wo.filter_ignoreCase ? + data.exact.toLowerCase() : data.exact; + data.isMatch = tsf.matchType( c, columnIndex ); + + result = showRow; // if showRow is true, show that row + + // in case select filter option has a different value vs text 'a - z|A through Z' + ffxn = wo.filter_columnFilters ? + c.$filters.add( c.$externalFilters ) + .filter( '[data-column="' + columnIndex + '"]' ) + .find( 'select option:selected' ) + .attr( 'data-function-name' ) || '' : ''; + // replace accents - see #357 + if ( c.sortLocaleCompare ) { + data.filter = ts.replaceAccents( data.filter ); + } + + // replace column specific default filters - see #1088 + if ( wo.filter_defaultFilter && tsfRegex.iQuery.test( vars.defaultColFilter[ columnIndex ] ) ) { + data.filter = tsf.defaultFilter( data.filter, vars.defaultColFilter[ columnIndex ] ); + } + + // data.iFilter = case insensitive ( if wo.filter_ignoreCase is true ), + // data.filter = case sensitive + data.iFilter = wo.filter_ignoreCase ? ( data.filter || '' ).toLowerCase() : data.filter; + fxn = vars.functions[ columnIndex ]; + filterMatched = null; + if ( fxn ) { + if ( fxn === true ) { + // default selector uses exact match unless 'filter-match' class is found + filterMatched = data.isMatch ? + // data.iExact may be a number + ( '' + data.iExact ).search( data.iFilter ) >= 0 : + data.filter === data.exact; + } else if ( typeof fxn === 'function' ) { + // filter callback( exact cell content, parser normalized content, + // filter input value, column index, jQuery row object ) + filterMatched = fxn( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); + } else if ( typeof fxn[ ffxn || data.filter ] === 'function' ) { + // selector option function + txt = ffxn || data.filter; + filterMatched = + fxn[ txt ]( data.exact, data.cache, data.filter, columnIndex, data.$row, c, data ); + } + } + if ( filterMatched === null ) { + // cycle through the different filters + // filters return a boolean or null if nothing matches + filterMatched = tsf.processTypes( c, data, vars ); + if ( filterMatched !== null ) { + result = filterMatched; + // Look for match, and add child row data for matching + } else { + txt = ( data.iExact + data.childRowText ).indexOf( tsf.parseFilter( c, data.iFilter, data ) ); + result = ( ( !wo.filter_startsWith && txt >= 0 ) || ( wo.filter_startsWith && txt === 0 ) ); + } + } else { + result = filterMatched; + } + showRow = ( result ) ? showRow : false; + } + } + return showRow; + }, + findRows: function( table, filters, combinedFilters ) { + if ( table.config.lastCombinedFilter === combinedFilters || + !table.config.widgetOptions.filter_initialized ) { + return; + } + var len, norm_rows, rowData, $rows, $row, rowIndex, tbodyIndex, $tbody, columnIndex, + isChild, childRow, lastSearch, showRow, showParent, time, val, indx, + notFiltered, searchFiltered, query, injected, res, id, txt, + storedFilters = $.extend( [], filters ), + c = table.config, + wo = c.widgetOptions, + // data object passed to filters; anyMatch is a flag for the filters + data = { + anyMatch: false, + filters: filters, + // regex filter type cache + filter_regexCache : [] + }, + vars = { + // anyMatch really screws up with these types of filters + noAnyMatch: [ 'range', 'notMatch', 'operators' ], + // cache filter variables that use ts.getColumnData in the main loop + functions : [], + excludeFilter : [], + defaultColFilter : [], + defaultAnyFilter : ts.getColumnData( table, wo.filter_defaultFilter, c.columns, true ) || '' + }; + + // parse columns after formatter, in case the class is added at that point + data.parsed = []; + for ( columnIndex = 0; columnIndex < c.columns; columnIndex++ ) { + data.parsed[ columnIndex ] = wo.filter_useParsedData || + // parser has a "parsed" parameter + ( c.parsers && c.parsers[ columnIndex ] && c.parsers[ columnIndex ].parsed || + // getData may not return 'parsed' if other 'filter-' class names exist + // ( e.g. ) + ts.getData && ts.getData( c.$headerIndexed[ columnIndex ], + ts.getColumnData( table, c.headers, columnIndex ), 'filter' ) === 'parsed' || + c.$headerIndexed[ columnIndex ].hasClass( 'filter-parsed' ) ); + + vars.functions[ columnIndex ] = + ts.getColumnData( table, wo.filter_functions, columnIndex ) || + c.$headerIndexed[ columnIndex ].hasClass( 'filter-select' ); + vars.defaultColFilter[ columnIndex ] = + ts.getColumnData( table, wo.filter_defaultFilter, columnIndex ) || ''; + vars.excludeFilter[ columnIndex ] = + ( ts.getColumnData( table, wo.filter_excludeFilter, columnIndex, true ) || '' ).split( /\s+/ ); + } + + if ( c.debug ) { + console.log( 'Filter: Starting filter widget search', filters ); + time = new Date(); + } + // filtered rows count + c.filteredRows = 0; + c.totalRows = 0; + // combindedFilters are undefined on init + combinedFilters = ( storedFilters || [] ).join( '' ); + + for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { + $tbody = ts.processTbody( table, c.$tbodies.eq( tbodyIndex ), true ); + // skip child rows & widget added ( removable ) rows - fixes #448 thanks to @hempel! + // $rows = $tbody.children( 'tr' ).not( c.selectorRemove ); + columnIndex = c.columns; + // convert stored rows into a jQuery object + norm_rows = c.cache[ tbodyIndex ].normalized; + $rows = $( $.map( norm_rows, function( el ) { + return el[ columnIndex ].$row.get(); + }) ); + + if ( combinedFilters === '' || wo.filter_serversideFiltering ) { + $rows + .removeClass( wo.filter_filteredRow ) + .not( '.' + c.cssChildRow ) + .css( 'display', '' ); + } else { + // filter out child rows + $rows = $rows.not( '.' + c.cssChildRow ); + len = $rows.length; + + if ( ( wo.filter_$anyMatch && wo.filter_$anyMatch.length ) || + typeof filters[c.columns] !== 'undefined' ) { + data.anyMatchFlag = true; + data.anyMatchFilter = '' + ( + filters[ c.columns ] || + wo.filter_$anyMatch && tsf.getLatestSearch( wo.filter_$anyMatch ).val() || + '' + ); + if ( wo.filter_columnAnyMatch ) { + // specific columns search + query = data.anyMatchFilter.split( tsfRegex.andSplit ); + injected = false; + for ( indx = 0; indx < query.length; indx++ ) { + res = query[ indx ].split( ':' ); + if ( res.length > 1 ) { + // make the column a one-based index ( non-developers start counting from one :P ) + id = parseInt( res[0], 10 ) - 1; + if ( id >= 0 && id < c.columns ) { // if id is an integer + filters[ id ] = res[1]; + query.splice( indx, 1 ); + indx--; + injected = true; + } + } + } + if ( injected ) { + data.anyMatchFilter = query.join( ' && ' ); + } + } + } + + // optimize searching only through already filtered rows - see #313 + searchFiltered = wo.filter_searchFiltered; + lastSearch = c.lastSearch || c.$table.data( 'lastSearch' ) || []; + if ( searchFiltered ) { + // cycle through all filters; include last ( columnIndex + 1 = match any column ). Fixes #669 + for ( indx = 0; indx < columnIndex + 1; indx++ ) { + val = filters[indx] || ''; + // break out of loop if we've already determined not to search filtered rows + if ( !searchFiltered ) { indx = columnIndex; } + // search already filtered rows if... + searchFiltered = searchFiltered && lastSearch.length && + // there are no changes from beginning of filter + val.indexOf( lastSearch[indx] || '' ) === 0 && + // if there is NOT a logical 'or', or range ( 'to' or '-' ) in the string + !tsfRegex.alreadyFiltered.test( val ) && + // if we are not doing exact matches, using '|' ( logical or ) or not '!' + !tsfRegex.exactTest.test( val ) && + // don't search only filtered if the value is negative + // ( '> -10' => '> -100' will ignore hidden rows ) + !( tsfRegex.isNeg1.test( val ) || tsfRegex.isNeg2.test( val ) ) && + // if filtering using a select without a 'filter-match' class ( exact match ) - fixes #593 + !( val !== '' && c.$filters && c.$filters.filter( '[data-column="' + indx + '"]' ).find( 'select' ).length && + !tsf.matchType( c, indx ) ); + } + } + notFiltered = $rows.not( '.' + wo.filter_filteredRow ).length; + // can't search when all rows are hidden - this happens when looking for exact matches + if ( searchFiltered && notFiltered === 0 ) { searchFiltered = false; } + if ( c.debug ) { + console.log( 'Filter: Searching through ' + + ( searchFiltered && notFiltered < len ? notFiltered : 'all' ) + ' rows' ); + } + if ( data.anyMatchFlag ) { + if ( c.sortLocaleCompare ) { + // replace accents + data.anyMatchFilter = ts.replaceAccents( data.anyMatchFilter ); + } + if ( wo.filter_defaultFilter && tsfRegex.iQuery.test( vars.defaultAnyFilter ) ) { + data.anyMatchFilter = tsf.defaultFilter( data.anyMatchFilter, vars.defaultAnyFilter ); + // clear search filtered flag because default filters are not saved to the last search + searchFiltered = false; + } + // make iAnyMatchFilter lowercase unless both filter widget & core ignoreCase options are true + // when c.ignoreCase is true, the cache contains all lower case data + data.iAnyMatchFilter = !( wo.filter_ignoreCase && c.ignoreCase ) ? + data.anyMatchFilter : + data.anyMatchFilter.toLowerCase(); + } + + // loop through the rows + for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { + + txt = $rows[ rowIndex ].className; + // the first row can never be a child row + isChild = rowIndex && tsfRegex.child.test( txt ); + // skip child rows & already filtered rows + if ( isChild || ( searchFiltered && tsfRegex.filtered.test( txt ) ) ) { + continue; + } + + data.$row = $rows.eq( rowIndex ); + data.cacheArray = norm_rows[ rowIndex ]; + rowData = data.cacheArray[ c.columns ]; + data.rawArray = rowData.raw; + data.childRowText = ''; + + if ( !wo.filter_childByColumn ) { + txt = ''; + // child row cached text + childRow = rowData.child; + // so, if 'table.config.widgetOptions.filter_childRows' is true and there is + // a match anywhere in the child row, then it will make the row visible + // checked here so the option can be changed dynamically + for ( indx = 0; indx < childRow.length; indx++ ) { + txt += ' ' + childRow[indx].join( ' ' ) || ''; + } + data.childRowText = wo.filter_childRows ? + ( wo.filter_ignoreCase ? txt.toLowerCase() : txt ) : + ''; + } + + showRow = false; + showParent = tsf.processRow( c, data, vars ); + $row = rowData.$row; + + // don't pass reference to val + val = showParent ? true : false; + childRow = rowData.$row.filter( ':gt(0)' ); + if ( wo.filter_childRows && childRow.length ) { + if ( wo.filter_childByColumn ) { + if ( !wo.filter_childWithSibs ) { + // hide all child rows + childRow.addClass( wo.filter_filteredRow ); + // if only showing resulting child row, only include parent + $row = $row.eq( 0 ); + } + // cycle through each child row + for ( indx = 0; indx < childRow.length; indx++ ) { + data.$row = childRow.eq( indx ); + data.cacheArray = rowData.child[ indx ]; + data.rawArray = data.cacheArray; + val = tsf.processRow( c, data, vars ); + // use OR comparison on child rows + showRow = showRow || val; + if ( !wo.filter_childWithSibs && val ) { + childRow.eq( indx ).removeClass( wo.filter_filteredRow ); + } + } + } + // keep parent row match even if no child matches... see #1020 + showRow = showRow || showParent; + } else { + showRow = val; + } + $row + .toggleClass( wo.filter_filteredRow, !showRow )[0] + .display = showRow ? '' : 'none'; + } + } + c.filteredRows += $rows.not( '.' + wo.filter_filteredRow ).length; + c.totalRows += $rows.length; + ts.processTbody( table, $tbody, false ); + } + c.lastCombinedFilter = combinedFilters; // save last search + // don't save 'filters' directly since it may have altered ( AnyMatch column searches ) + c.lastSearch = storedFilters; + c.$table.data( 'lastSearch', storedFilters ); + if ( wo.filter_saveFilters && ts.storage ) { + ts.storage( table, 'tablesorter-filters', tsf.processFilters( storedFilters, true ) ); + } + if ( c.debug ) { + console.log( 'Completed filter widget search' + ts.benchmark(time) ); + } + if ( wo.filter_initialized ) { + c.$table.triggerHandler( 'filterBeforeEnd', c ); + c.$table.triggerHandler( 'filterEnd', c ); + } + setTimeout( function() { + ts.applyWidget( c.table ); // make sure zebra widget is applied + }, 0 ); + }, + getOptionSource: function( table, column, onlyAvail ) { + table = $( table )[0]; + var c = table.config, + wo = c.widgetOptions, + arry = false, + source = wo.filter_selectSource, + last = c.$table.data( 'lastSearch' ) || [], + fxn = typeof source === 'function' ? true : ts.getColumnData( table, source, column ); + + if ( onlyAvail && last[column] !== '' ) { + onlyAvail = false; + } + + // filter select source option + if ( fxn === true ) { + // OVERALL source + arry = source( table, column, onlyAvail ); + } else if ( fxn instanceof $ || ( $.type( fxn ) === 'string' && fxn.indexOf( '' ) >= 0 ) ) { + // selectSource is a jQuery object or string of options + return fxn; + } else if ( $.isArray( fxn ) ) { + arry = fxn; + } else if ( $.type( source ) === 'object' && fxn ) { + // custom select source function for a SPECIFIC COLUMN + arry = fxn( table, column, onlyAvail ); + } + if ( arry === false ) { + // fall back to original method + arry = tsf.getOptions( table, column, onlyAvail ); + } + + return tsf.processOptions( table, column, arry ); + + }, + processOptions: function( table, column, arry ) { + if ( !$.isArray( arry ) ) { + return false; + } + table = $( table )[0]; + var cts, txt, indx, len, parsedTxt, str, + c = table.config, + validColumn = typeof column !== 'undefined' && column !== null && column >= 0 && column < c.columns, + parsed = []; + // get unique elements and sort the list + // if $.tablesorter.sortText exists ( not in the original tablesorter ), + // then natural sort the list otherwise use a basic sort + arry = $.grep( arry, function( value, indx ) { + if ( value.text ) { + return true; + } + return $.inArray( value, arry ) === indx; + }); + if ( validColumn && c.$headerIndexed[ column ].hasClass( 'filter-select-nosort' ) ) { + // unsorted select options + return arry; + } else { + len = arry.length; + // parse select option values + for ( indx = 0; indx < len; indx++ ) { + txt = arry[ indx ]; + // check for object + str = txt.text ? txt.text : txt; + // sortNatural breaks if you don't pass it strings + parsedTxt = ( validColumn && c.parsers && c.parsers.length && + c.parsers[ column ].format( str, table, [], column ) || str ).toString(); + parsedTxt = c.widgetOptions.filter_ignoreCase ? parsedTxt.toLowerCase() : parsedTxt; + // parse array data using set column parser; this DOES NOT pass the original + // table cell to the parser format function + if ( txt.text ) { + txt.parsed = parsedTxt; + parsed[ parsed.length ] = txt; + } else { + parsed[ parsed.length ] = { + text : txt, + // check parser length - fixes #934 + parsed : parsedTxt + }; + } + } + // sort parsed select options + cts = c.textSorter || ''; + parsed.sort( function( a, b ) { + var x = a.parsed, + y = b.parsed; + if ( validColumn && typeof cts === 'function' ) { + // custom OVERALL text sorter + return cts( x, y, true, column, table ); + } else if ( validColumn && typeof cts === 'object' && cts.hasOwnProperty( column ) ) { + // custom text sorter for a SPECIFIC COLUMN + return cts[column]( x, y, true, column, table ); + } else if ( ts.sortNatural ) { + // fall back to natural sort + return ts.sortNatural( x, y ); + } + // using an older version! do a basic sort + return true; + }); + // rebuild arry from sorted parsed data + arry = []; + len = parsed.length; + for ( indx = 0; indx < len; indx++ ) { + arry[ arry.length ] = parsed[indx]; + } + return arry; + } + }, + getOptions: function( table, column, onlyAvail ) { + table = $( table )[0]; + var rowIndex, tbodyIndex, len, row, cache, indx, child, childLen, + c = table.config, + wo = c.widgetOptions, + arry = []; + for ( tbodyIndex = 0; tbodyIndex < c.$tbodies.length; tbodyIndex++ ) { + cache = c.cache[tbodyIndex]; + len = c.cache[tbodyIndex].normalized.length; + // loop through the rows + for ( rowIndex = 0; rowIndex < len; rowIndex++ ) { + // get cached row from cache.row ( old ) or row data object + // ( new; last item in normalized array ) + row = cache.row ? + cache.row[ rowIndex ] : + cache.normalized[ rowIndex ][ c.columns ].$row[0]; + // check if has class filtered + if ( onlyAvail && row.className.match( wo.filter_filteredRow ) ) { + continue; + } + // get non-normalized cell content + if ( wo.filter_useParsedData || + c.parsers[column].parsed || + c.$headerIndexed[column].hasClass( 'filter-parsed' ) ) { + arry[ arry.length ] = '' + cache.normalized[ rowIndex ][ column ]; + // child row parsed data + if ( wo.filter_childRows && wo.filter_childByColumn ) { + childLen = cache.normalized[ rowIndex ][ c.columns ].$row.length - 1; + for ( indx = 0; indx < childLen; indx++ ) { + arry[ arry.length ] = '' + cache.normalized[ rowIndex ][ c.columns ].child[ indx ][ column ]; + } + } + } else { + // get raw cached data instead of content directly from the cells + arry[ arry.length ] = cache.normalized[ rowIndex ][ c.columns ].raw[ column ]; + // child row unparsed data + if ( wo.filter_childRows && wo.filter_childByColumn ) { + childLen = cache.normalized[ rowIndex ][ c.columns ].$row.length; + for ( indx = 1; indx < childLen; indx++ ) { + child = cache.normalized[ rowIndex ][ c.columns ].$row.eq( indx ).children().eq( column ); + arry[ arry.length ] = '' + ts.getElementText( c, child, column ); + } + } + } + } + } + return arry; + }, + buildSelect: function( table, column, arry, updating, onlyAvail ) { + table = $( table )[0]; + column = parseInt( column, 10 ); + if ( !table.config.cache || $.isEmptyObject( table.config.cache ) ) { + return; + } + + var indx, val, txt, t, $filters, $filter, option, + c = table.config, + wo = c.widgetOptions, + node = c.$headerIndexed[ column ], + // t.data( 'placeholder' ) won't work in jQuery older than 1.4.3 + options = '', + // Get curent filter value + currentValue = c.$table + .find( 'thead' ) + .find( 'select.' + tscss.filter + '[data-column="' + column + '"]' ) + .val(); + + // nothing included in arry ( external source ), so get the options from + // filter_selectSource or column data + if ( typeof arry === 'undefined' || arry === '' ) { + arry = tsf.getOptionSource( table, column, onlyAvail ); + } + + if ( $.isArray( arry ) ) { + // build option list + for ( indx = 0; indx < arry.length; indx++ ) { + option = arry[ indx ]; + if ( option.text ) { + // OBJECT!! add data-function-name in case the value is set in filter_functions + option['data-function-name'] = typeof option.value === 'undefined' ? option.text : option.value; + + // support jQuery < v1.8, otherwise the below code could be shortened to + // options += $( '