OSDN Git Service

*Add new feature Additional device data from TSV file conf) ChxjAddDeviceDataTSV...
authorcoltware <coltware@94a86f0a-4377-0410-8349-a3afc59ff858>
Fri, 19 Feb 2010 15:58:54 +0000 (15:58 +0000)
committerAtsushi Konno <konn@users.sourceforge.jp>
Thu, 13 May 2010 16:27:18 +0000 (01:27 +0900)
  tsv sample)
  PROVIDER DEVICE_ID WIDTH HEIGHT HTML_TYPE  FOO1
 1      F01A   240    352     IXHTML    Y

  feature 1. ) override device spec from TSV ( key is provider and device id )
    Booked header key name
       Provider  ) 1: docomo, 2: au, 3:SoftBank, 0:unknown
        Device_id )   same xml
Device_name ) same xml
        Html_spec_type ) same xml
width ) same xml
heigh ) same xml ( alias: height )
       wp_width ) same xml
wp_heigh ) same xml ( alias : wp_height )
gif ) same xml
jpeg ) same xml
png ) same xml
bmp2) same xml
bmp4) same xml
color) same xml
cache) same xml

  feature 2. ) Add request header
ex )
        X-Chxj-Info-FOO1 = Y
        if you use PHP, you can get value form $_SERVER['HTTP_X_CHXJ_INFO_FOO1'].

  Note:
     Tsv spec data overrides device XML data.
     allows tsv null value ( so if you use xml device data, please put null value )

     Typical usage:
        ChxjLoadDeviceData   .../simple_device_data.xml
ChxjAddDeviceDataTSV path_to_tsv_file

include/chxj_add_device_env.h [changed mode: 0644->0755]
include/chxj_load_device_data.h [changed mode: 0644->0755]
include/chxj_specified_device.h [changed mode: 0644->0755]
include/mod_chxj.h [changed mode: 0644->0755]
src/chxj_add_device_env.c [changed mode: 0644->0755]
src/chxj_load_device_data.c [changed mode: 0644->0755]
src/chxj_specified_device.c [changed mode: 0644->0755]
src/mod_chxj.c

old mode 100644 (file)
new mode 100755 (executable)
index 82cae00..acb1bd5
@@ -68,6 +68,8 @@
 
 #define HTTP_X_CHXJ_VERSION  "X-Chxj-Version"
 
+#define HTTP_X_CHXJ_INFO     "X-Chxj-Info"
+
 extern void chxj_add_device_env(request_rec *r, device_table *spec);
 
 #endif
old mode 100644 (file)
new mode 100755 (executable)
index ca036f5..66811c3
@@ -24,4 +24,9 @@ extern void chxj_load_device_data(
   apr_pool_t*              p, 
   mod_chxj_config*         conf);
 
+extern void chxj_load_device_tsv_data(
+  apr_file_t*              fp,
+  apr_pool_t*              p, 
+  mod_chxj_config*         conf);
+
 #endif
old mode 100644 (file)
new mode 100755 (executable)
index 9d6abd9..459e450
@@ -44,6 +44,7 @@ typedef struct device_table_t device_table;
 
 struct device_table_t {
   struct device_table_t* next;
+  int                    provider;              /* DOCOMO|AU|SOFTBANK|UNKNOWN */
   const char*            device_id;
   const char*            device_name;
   spec_type              html_spec_type;
@@ -128,4 +129,13 @@ extern device_table* chxj_specified_device(
   request_rec             *r, 
   const char              *user_agent);
 
+extern device_table* chxj_specified_device_from_xml(
+  request_rec             *r,
+  const char              *user_agent);
+
+extern device_table* chxj_specified_device_from_tsv(
+    request_rec           *r,
+    device_table          *spec,
+    const char            *user_agent);
+
 #endif
old mode 100644 (file)
new mode 100755 (executable)
index 42c69cb..a043e60
@@ -43,6 +43,7 @@
 #include "apr_dso.h"
 #include "apr_general.h"
 #include "apr_pools.h"
+#include "apr_hash.h"
 
 #if defined(AP_NEED_SET_MUTEX_PERMS)
 #  include "unixd.h"
@@ -351,6 +352,8 @@ struct mod_chxj_config {
   cookie_store_type_t   cookie_store_type;
   int                   cookie_lazy_mode;
   char                  *cookie_dbm_type;
+  
+  int                   detect_device_type; /* XML|TSV */
 
 #if defined(USE_MYSQL_COOKIE)
   mysql_t               mysql;
@@ -366,6 +369,9 @@ struct mod_chxj_config {
   chxj_new_line_type_t  new_line_type;
 
   char                  *post_log;              /* post log environment name. */
+  
+  apr_array_header_t    *device_keys;           /* TSV header array */
+  apr_hash_t            *device_hash;           /* TSV device data hash table */
 };
 
 #define IS_COOKIE_STORE_DBM(X)      ((X) == COOKIE_STORE_TYPE_DBM)
@@ -462,6 +468,14 @@ module AP_MODULE_DECLARE_DATA chxj_module;
 #define CHXJ_IMODE_EMOJI_COLOR_OFF  (1)
 #define CHXJ_IMODE_EMOJI_COLOR_NONE (0)
 
+#define CHXJ_ADD_DETECT_DEVICE_TYPE_TSV     (1)
+#define CHXJ_ADD_DETECT_DEVICE_TYPE_NONE    (0)
+
+#define CHXJ_PROVIDER_UNKNOWN  (0)
+#define CHXJ_PROVIDER_DOCOMO   (1)
+#define CHXJ_PROVIDER_AU       (2)
+#define CHXJ_PROVIDER_SOFTBANK (3)
+
 
 #define DBG(X,args...)  chxj_log_rerror(APLOG_MARK,APLOG_DEBUG,0,(request_rec*)(X),##args)
 #define SDBG(X,Y)       chxj_log_error(APLOG_MARK,APLOG_DEBUG,0,(X),(Y))
old mode 100644 (file)
new mode 100755 (executable)
index 4fa865f..82f8368
@@ -79,6 +79,28 @@ chxj_add_device_env(request_rec *r, device_table *spec)
   apr_table_setn(r->headers_in, HTTP_X_CHXJ_CACHE,    apr_psprintf(r->pool, "%d", spec->cache));
 
   apr_table_setn(r->headers_in, HTTP_X_CHXJ_VERSION,  apr_pstrdup(r->pool, PACKAGE_VERSION));
+  
+  mod_chxj_config*    dconf;
+  dconf = chxj_get_module_config(r->per_dir_config, &chxj_module);
+  if (dconf->detect_device_type == CHXJ_ADD_DETECT_DEVICE_TYPE_TSV ){
+    if (spec->device_id     != NULL &&
+        dconf->device_hash  != NULL){
+      char *key = apr_psprintf(r->pool,"%d.%s",spec->provider,spec->device_id);
+      apr_table_t *ht = apr_hash_get(dconf->device_hash,key,APR_HASH_KEY_STRING);
+  
+      if(ht != NULL){
+        int i;
+        for ( i=0; i< dconf->device_keys->nelts; i++){
+          const char *k = ((const char**)dconf->device_keys->elts)[i];
+          char *val = (char *)apr_table_get(ht,k);
+          if(val != NULL){
+            char *info_key = apr_psprintf(r->pool,"%s-%s",HTTP_X_CHXJ_INFO,k);
+            apr_table_setn(r->headers_in, info_key, val);
+          }
+        }
+      }
+    }
+  }
 
   DBG(r, "REQ[%X] end chxj_add_device_env()", (unsigned int)(apr_size_t)r);
 }
old mode 100644 (file)
new mode 100755 (executable)
index 72bb193..980d43d
@@ -136,6 +136,7 @@ s_set_device_data(Doc *doc, apr_pool_t *p, device_table_list *dtl, Node *node)
 
   dt = apr_pcalloc(p, sizeof(device_table));
   dt->next           = NULL;
+  dt->provider       = CHXJ_PROVIDER_UNKNOWN;
   dt->device_id      = NULL;
   dt->device_name    = NULL;
   dt->html_spec_type = CHXJ_SPEC_Chtml_3_0;
@@ -218,36 +219,47 @@ s_set_device_data(Doc *doc, apr_pool_t *p, device_table_list *dtl, Node *node)
           char *vv = qs_get_node_value(doc, ch);
           if (STRCASEEQ('x','X',"xhtml_mobile_1_0",vv)) {
             dt->html_spec_type = CHXJ_SPEC_XHtml_Mobile_1_0;
+            dt->provider       = CHXJ_PROVIDER_AU;
           }
           else if (STRCASEEQ('c','C',"chtml_1_0",vv)) {
             dt->html_spec_type = CHXJ_SPEC_Chtml_1_0;
+            dt->provider       = CHXJ_PROVIDER_DOCOMO;
           }
           else if (STRCASEEQ('c','C',"chtml_2_0",vv)) {
             dt->html_spec_type = CHXJ_SPEC_Chtml_2_0;
+            dt->provider       = CHXJ_PROVIDER_DOCOMO;
           }
           else if (STRCASEEQ('c','C',"chtml_3_0",vv)) {
             dt->html_spec_type = CHXJ_SPEC_Chtml_3_0;
+            dt->provider       = CHXJ_PROVIDER_DOCOMO;
           }
           else if (STRCASEEQ('c','C',"chtml_4_0",vv)) {
             dt->html_spec_type = CHXJ_SPEC_Chtml_4_0;
+            dt->provider       = CHXJ_PROVIDER_DOCOMO;
           }
           else if (STRCASEEQ('c','C',"chtml_5_0",vv)) {
             dt->html_spec_type = CHXJ_SPEC_Chtml_5_0;
+            dt->provider       = CHXJ_PROVIDER_DOCOMO;
           }
           else if (STRCASEEQ('c','C',"chtml_6_0",vv)) {
             dt->html_spec_type = CHXJ_SPEC_Chtml_6_0;
+            dt->provider       = CHXJ_PROVIDER_DOCOMO;
           }
           else if (STRCASEEQ('c','C',"chtml_7_0",vv)) {
             dt->html_spec_type = CHXJ_SPEC_Chtml_7_0;
+            dt->provider       = CHXJ_PROVIDER_DOCOMO;
           }
           else if (STRCASEEQ('h','H',"hdml",vv)) {
             dt->html_spec_type = CHXJ_SPEC_Hdml;
+            dt->provider       = CHXJ_PROVIDER_AU;
           }
           else if (STRCASEEQ('j','J',"jhtml",vv)) {
             dt->html_spec_type = CHXJ_SPEC_Jhtml;
+            dt->provider       = CHXJ_PROVIDER_SOFTBANK;
           }
           else if (STRCASEEQ('j','J',"jxhtml",vv)) {
             dt->html_spec_type = CHXJ_SPEC_Jxhtml;
+            dt->provider       = CHXJ_PROVIDER_SOFTBANK;
           }
         }
       }
@@ -456,6 +468,87 @@ s_set_device_data(Doc *doc, apr_pool_t *p, device_table_list *dtl, Node *node)
     }
   }
 }
+
+/**
+ * load device_data.xml
+ */
+void
+chxj_load_device_tsv_data(apr_file_t *fp,apr_pool_t *p, mod_chxj_config *conf) 
+{
+  apr_status_t st;
+  
+  char *pstat;
+  char *pair;
+  
+  char *line = apr_palloc(p,256);
+  
+  int is_header = 1;
+  int keynum = 0;
+  int valnum = 0;
+  
+  conf->device_keys = apr_array_make(p,2, sizeof(const char*));
+  conf->device_hash = apr_hash_make(p);
+  
+  while(APR_EOF != (st=apr_file_eof(fp))){
+    st = apr_file_gets(line,1024,fp);
+    if(st == APR_SUCCESS){
+      if(is_header){
+        keynum=0;
+        
+        for(pair = apr_strtok(line,"\t",&pstat); pair != NULL; pair = apr_strtok(NULL,"\t",&pstat)){
+          apr_collapse_spaces(pair,pair);
+          if(keynum < 128){
+            *(const char**)apr_array_push(conf->device_keys) = apr_pstrdup(p,pair);
+          }
+          
+          //ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, "V[[%s]]", pair);
+          keynum++;
+        }
+        is_header = 0;
+      }
+      else{
+        apr_table_t *tsv_table = apr_table_make(p,keynum);
+        valnum = 0;
+        char *uid = NULL;
+        char *provider = NULL;
+        for(pair = apr_strtok(line,"\t",&pstat); pair != NULL; pair = apr_strtok(NULL,"\t",&pstat)){
+          apr_collapse_spaces(pair,pair);
+          if(valnum < keynum){
+            const char *kn = ((const char**)conf->device_keys->elts)[valnum];
+            if(strcasecmp("-",pair) != 0){
+              apr_table_set(tsv_table,kn,pair);
+              if(strcasecmp(kn,"device_id") == 0){
+                uid = apr_pstrdup(p,pair);
+              }
+              else if(strcasecmp(kn,"provider") == 0){
+                if(strcasecmp(pair,"docomo") == 0){
+                  provider = "1";
+                }
+                else if(strcasecmp(pair,"au") == 0){
+                  provider = "2";
+                }
+                else if(strcasecmp(pair,"softbank") == 0){
+                  provider = "3";
+                }
+                else{
+                  provider = apr_pstrdup(p,pair);
+                }
+              }
+              //ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, "[%s] %d:V[%s] = [%s]", uid,valnum,kn,pair);
+            }
+          }
+          valnum++;
+        }
+        if(uid != NULL && *uid && provider != NULL && *provider){
+          char *key = apr_psprintf(p,"%s.%s",provider,uid);
+          //ap_log_error(APLOG_MARK, APLOG_CRIT, 0, NULL, "save hash [%s]",key );
+          apr_hash_set(conf->device_hash,key,APR_HASH_KEY_STRING,tsv_table);
+        }
+      }
+    }
+  }
+}
+
 /*
  * vim:ts=2 et
  */
old mode 100644 (file)
new mode 100755 (executable)
index 68247ec..e37ace7
@@ -18,6 +18,7 @@
 
 static device_table  UNKNOWN_DEVICE      = {
   .next = NULL,
+  .provider  = CHXJ_PROVIDER_UNKNOWN,
   .device_id = "",
   .device_name = "UNKNOWN",
   .html_spec_type = CHXJ_SPEC_UNKNOWN,
@@ -58,6 +59,27 @@ static device_table  UNKNOWN_DEVICE      = {
   .emoji_type = NULL,
 };
 
+int
+get_boolean_value(request_rec *r,const char *val){
+  switch(*val) {
+    case '0':
+    case 'f':
+    case 'F':
+    case 'n':
+    case 'N':
+      return 0;
+      break;
+    case '1':
+    case 't':
+    case 'T':
+    case 'y':
+    case 'Y':
+      return 1;
+      break;
+  }
+  return -1;
+}
+
 /**
  * The device is specified from UserAgent. 
  * @param r Request_rec is appointed.
@@ -67,6 +89,36 @@ static device_table  UNKNOWN_DEVICE      = {
 device_table *
 chxj_specified_device(request_rec *r, const char *user_agent) 
 {
+  device_table         *returnType = &UNKNOWN_DEVICE;
+  device_table         *dt;
+  mod_chxj_config      *conf;
+  
+  conf = chxj_get_module_config(r->per_dir_config, &chxj_module);
+  
+  DBG(r, "start chxj_specified_device() %d",conf->detect_device_type);
+  
+  if(! user_agent){
+    return returnType;
+  }
+  
+  dt = chxj_specified_device_from_xml(r,user_agent);
+
+  if (conf->detect_device_type == CHXJ_ADD_DETECT_DEVICE_TYPE_TSV ){
+    chxj_specified_device_from_tsv(r,dt,user_agent);
+  }
+  
+  DBG(r, "end chxj_specified_device() %d",conf->detect_device_type);
+  return dt;
+}
+/**
+ * The device is specified from UserAgent. 
+ * @param r Request_rec is appointed.
+ * @param userAgent UserAgent is appointed here,
+ * @return The style which corresponds is returned.
+ */
+device_table *
+chxj_specified_device_from_xml(request_rec *r, const char *user_agent) 
+{
   ap_regmatch_t        match[10];
   device_table         *returnType = &UNKNOWN_DEVICE;
   device_table_list    *dtl;
@@ -115,8 +167,12 @@ chxj_specified_device(request_rec *r, const char *user_agent)
             break;
         }
 
-        if (dt)
+        if (dt){
+          if (conf->detect_device_type > CHXJ_ADD_DETECT_DEVICE_TYPE_NONE ){
+            dt->device_id = device_id;
+          }
           returnType = dt;
+        }
       }
     }
 
@@ -130,6 +186,159 @@ chxj_specified_device(request_rec *r, const char *user_agent)
 
   return returnType;
 }
+/**
+ * The device is specified from TSV file. 
+ * @param r Request_rec is appointed.
+ * @param userAgent UserAgent is appointed here,
+ * @return The style which corresponds is returned.
+ */
+device_table *
+chxj_specified_device_from_tsv(request_rec *r,device_table *spec,const char *user_agent)
+{
+  if(spec->device_id == NULL){
+    return spec;
+  }
+  
+  DBG(r, "start chxj_specified_device_from_tsv() device_id:[%s]",spec->device_id);
+  mod_chxj_config      *conf;
+  
+  conf = chxj_get_module_config(r->per_dir_config, &chxj_module);
+  
+  if(conf->device_hash == NULL){
+    return spec;
+  }
+  char *key = apr_psprintf(r->pool,"%d.%s",spec->provider,spec->device_id);
+  
+  apr_table_t *ht = apr_hash_get(conf->device_hash,key,APR_HASH_KEY_STRING);
+  
+  if(ht != NULL){
+    DBG(r, "found ! chxj_specified_device_from_tsv() %s",key);
+    int i;
+    for ( i=0; i< conf->device_keys->nelts; i++){
+      const char *k = ((const char**)conf->device_keys->elts)[i];
+      char *val = (char *)apr_table_get(ht,k);
+      if(val == NULL){
+        continue;
+      }
+      DBG(r, "start chxj_specified_device_from_tsv() [%s] = [%s]:[%s]",spec->device_id,k,val);
+      if (STRCASEEQ('d','D',"device_name",k)){
+        spec->device_name = apr_pstrdup(r->pool,val);
+      }
+      if (STRCASEEQ('w','W',"width",k)){
+        if(chxj_chk_numeric(val) == 0){
+          spec->width = chxj_atoi(val);
+        }
+      }
+      else if (STRCASEEQ('h','H',"heigh",k)){
+        if(chxj_chk_numeric(val) == 0){
+          spec->heigh = chxj_atoi(val);
+        }
+      }
+      else if (STRCASEEQ('h','H',"height",k)){
+        if(chxj_chk_numeric(val) == 0){
+          spec->heigh = chxj_atoi(val);
+        }
+      }
+      else if (STRCASEEQ('w','W',"wp_width",k)){
+        if(chxj_chk_numeric(val) == 0){
+          spec->wp_width = chxj_atoi(val);
+        }
+      }
+      else if (STRCASEEQ('w','W',"wp_heigh",k)){
+        if(chxj_chk_numeric(val) == 0){
+          spec->wp_heigh = chxj_atoi(val);
+        }
+      }
+      else if (STRCASEEQ('w','W',"wp_height",k)){
+        if(chxj_chk_numeric(val) == 0){
+          spec->wp_heigh = chxj_atoi(val);
+        }
+      }
+      else if (STRCASEEQ('c','C',"cache",k)){
+        if(chxj_chk_numeric(val) == 0){
+          spec->cache = chxj_atoi(val);
+        }
+      }
+      else if (STRCASEEQ('g','G',"gif",k)){
+        int tmp = get_boolean_value(r,val);
+        if(tmp > -1 ){
+            spec->available_gif = tmp;
+        }
+      }
+      else if (STRCASEEQ('j','J',"jpeg",k)){
+        int tmp = get_boolean_value(r,val);
+        if(tmp > -1 ){
+            spec->available_jpeg = tmp;
+        }
+      }
+      else if (STRCASEEQ('p','P',"png",k)){
+        int tmp = get_boolean_value(r,val);
+        if(tmp > -1 ){
+          spec->available_png = tmp;
+        }
+      }
+      else if (STRCASEEQ('b','B',"bmp2",k)){
+        int tmp = get_boolean_value(r,val);
+        if(tmp > -1 ){
+          spec->available_bmp2 = tmp;
+        }
+      }
+      else if (STRCASEEQ('b','B',"bmp4",k)){
+        int tmp = get_boolean_value(r,val);
+        if(tmp > -1 ){
+          spec->available_bmp4 = tmp;
+        }
+      }
+      else if (STRCASEEQ('c','C',"color",k)){
+        if(chxj_chk_numeric(val) == 0){
+          spec->color = chxj_atoi(val);
+        }
+      }
+      else if (STRCASEEQ('e','E',"emoji_type",k)){
+        spec->emoji_type = apr_pstrdup(r->pool,val);
+      }
+      else if (STRCASEEQ('h','H',"html_spec_type",k)){
+        if (STRCASEEQ('x','X',"xhtml_mobile_1_0",val)) {
+          spec->html_spec_type = CHXJ_SPEC_XHtml_Mobile_1_0;
+        }
+        else if (STRCASEEQ('c','C',"chtml_1_0",val)) {
+          spec->html_spec_type = CHXJ_SPEC_Chtml_1_0;
+        }
+        else if (STRCASEEQ('c','C',"chtml_2_0",val)) {
+          spec->html_spec_type = CHXJ_SPEC_Chtml_2_0;
+        }
+        else if (STRCASEEQ('c','C',"chtml_3_0",val)) {
+          spec->html_spec_type = CHXJ_SPEC_Chtml_3_0;
+        }
+        else if (STRCASEEQ('c','C',"chtml_4_0",val)) {
+          spec->html_spec_type = CHXJ_SPEC_Chtml_4_0;
+        }
+        else if (STRCASEEQ('c','C',"chtml_5_0",val)) {
+          spec->html_spec_type = CHXJ_SPEC_Chtml_5_0;
+        }
+        else if (STRCASEEQ('c','C',"chtml_6_0",val)) {
+          spec->html_spec_type = CHXJ_SPEC_Chtml_6_0;
+        }
+        else if (STRCASEEQ('c','C',"chtml_7_0",val)) {
+          spec->html_spec_type = CHXJ_SPEC_Chtml_7_0;
+        }
+        else if (STRCASEEQ('h','H',"hdml",val)) {
+          spec->html_spec_type = CHXJ_SPEC_Hdml;
+        }
+        else if (STRCASEEQ('j','J',"jhtml",val)) {
+          spec->html_spec_type = CHXJ_SPEC_Jhtml;
+        }
+        else if (STRCASEEQ('j','J',"jxhtml",val)) {
+          spec->html_spec_type = CHXJ_SPEC_Jxhtml;
+        }
+      }
+    }
+  }
+  
+  DBG(r, "end chxj_specified_device_from_tsv() [%d]",spec->provider);
+  return spec;
+}
+
 
 /*
  * vim:ts=2 et
index fd11779..b79cdde 100755 (executable)
@@ -33,6 +33,7 @@
 #include "apr_dso.h"
 #include "apr_general.h"
 #include "apr_pools.h"
+#include "apr_file_info.h"
 
 #include "mod_chxj.h"
 #include "chxj_encoding.h"
@@ -1748,6 +1749,9 @@ chxj_create_per_dir_config(apr_pool_t *p, char *arg)
   conf->cookie_store_type = COOKIE_STORE_TYPE_NONE;
   conf->cookie_lazy_mode  = 0;
   conf->cookie_dbm_type  = NULL;
+  
+  conf->detect_device_type = CHXJ_ADD_DETECT_DEVICE_TYPE_NONE;
+  
 #if defined(USE_MYSQL_COOKIE)
   memset((void *)&conf->mysql, 0, sizeof(mysql_t));
   conf->mysql.port       = MYSQL_PORT;
@@ -1810,6 +1814,9 @@ chxj_merge_per_dir_config(apr_pool_t *p, void *basev, void *addv)
   mrg->allowed_cookie_domain = NULL;
   mrg->post_log         = NULL;
   mrg->cookie_dbm_type  = NULL;
+  
+  mrg->device_keys      = NULL;
+  mrg->device_hash      = NULL;
 
   mrg->dir = apr_pstrdup(p, add->dir);
 
@@ -2055,6 +2062,27 @@ chxj_merge_per_dir_config(apr_pool_t *p, void *basev, void *addv)
     mrg->imode_emoji_color = add->imode_emoji_color;
   }
   
+  if (add->detect_device_type == CHXJ_ADD_DETECT_DEVICE_TYPE_NONE) {
+    mrg->detect_device_type = base->detect_device_type;
+  }
+  else {
+    mrg->detect_device_type = add->detect_device_type;
+  }
+  
+  if (add->device_keys) {
+    mrg->device_keys = add->device_keys;
+  }
+  else{
+    mrg->device_keys = base->device_keys;
+  }
+  
+  if (add->device_hash) {
+    mrg->device_hash = add->device_hash;
+  }
+  else{
+    mrg->device_hash = base->device_hash;
+  }
+  
   return mrg;
 }
 
@@ -2991,6 +3019,36 @@ cmd_imode_emoji_color(
   return NULL;
 }
 
+static const char *
+cmd_add_device_data_tsv(cmd_parms *parms, void *mconfig, const char *arg) 
+{
+  mod_chxj_config  *conf;
+  
+  if (strlen(arg) > 256) 
+    return "mod_chxj: device tsv filename too long.";
+
+  conf = (mod_chxj_config *)mconfig;
+  
+  conf->detect_device_type = CHXJ_ADD_DETECT_DEVICE_TYPE_TSV;
+  
+  apr_finfo_t info;
+  apr_status_t res = apr_stat(&info,arg,APR_FINFO_TYPE,parms->pool);
+  if(res != APR_SUCCESS){
+    return apr_psprintf(parms->pool,"ChxjDeviceTSV [%s]: not found ",arg);
+  }
+  else{
+    if(info.filetype != APR_REG ){
+      return apr_psprintf(parms->pool,"ChxjDeviceTSV [%s]: is not file ",arg);
+    }
+  }
+  apr_file_t *fp;
+  apr_file_open(&fp, arg, APR_READ|APR_BUFFERED, APR_OS_DEFAULT, parms->pool);
+  
+  chxj_load_device_tsv_data(fp,parms->pool,conf);
+  
+  apr_file_close(fp);
+  return NULL;
+}
 
 static const command_rec cmds[] = {
   AP_INIT_TAKE1(
@@ -3165,6 +3223,12 @@ static const command_rec cmds[] = {
     NULL,
     OR_ALL,
     "Auto i-mode emoji color"),
+  AP_INIT_TAKE1(
+    "ChxjAddDeviceDataTSV",
+    cmd_add_device_data_tsv,
+    NULL,
+    OR_ALL,
+    "Additional devices TSV data"),
   {NULL,{NULL},NULL,0,0,NULL},
 };