ファイル内の文字列置換作業をPythonで行う。

EclipseJavaソースの package名リファクタリングなど、マウスで操作していると
最近のEclipseは重いせいか、鬱陶しい作業だ。しかもマウスの手元が狂ったら泣きたくなる。
そういう時こそ Python スクリプトを走らせて実行させるべき。。

以下URLのページが参考になった。。
ディレクトリのファイルを再帰で検索する為に、、、
Pythonでフォルダ内のファイルリストを取得する
単語の置換の為に、、、
[file] Pythonを使ってファイル内のテキストを検索し置換える方法は? [text] [python-3.x] | CODE Q&A 問題解決 [日本語]

置換の関数

import os

def replace_word(infile, old_word, new_word):
    if not os.path.isfile(infile):
        print ("Error on replace_word, not a regular file : " + infile)
        sys.exit(1)
    f1 = open(infile, 'r', encoding='utf-8').read()
    f2 = open(infile, 'w', encoding='utf-8')
    f2.write(f1.replace(old_word, new_word))

glob 使う場合、、

import glob

for file in glob.glob('C:/pleiades/workspace/example/src/**/*.java', recursive=True):
    print("%s" % file)
    replace_word(file, "org.xxx.yyy", "jp.co.yyy")

pathlib 使う場合、

from pathlib import Path

p = Path("C:/pleiades/workspace/example/src/")
for file in p.glob("**/*.java"):
    print("%s" % file)
    replace_word(file, "org.xxx.yyy", "jp.co.yyy")

JavaScriptコードの中で、日付データのJSONとDateオブジェクトの相互変換

JavaScriptコードの中で日付データのJSONとDateオブジェクトの変換をどうしようと悩んでたら、
なるほどと思う記事を見つけた。
【JavaScript】JSON.stringify/parse をカスタマイズしてDate型とかを使えるようにする - 藤 遥のブログ
ありがたく書いて頂いてるのですが、惜しいのは書かれていた const 宣言関数は、

const CustomJSON = {
    replacer (key, value) {
       :

となっていて、replacer (key, value) "{" の間に、":" が抜けているので、IE11 ではエラーになってしまう。
  SCRIPT1003: ':'がありません
JSON.stringify / parse 第2引数にコールバックされる関数を与えることに思わず「なるほどそういう方法が。。」
そこで、もう少しでも汎用的にならないかと、parse はどうしても無理と思ったが stringify だけでもと思い書いた。

const JsonDate = {
   UTC:function(key, value){
      if (this[key] instanceof Date){
         return this[key].getTime();
      }
      return value;
   },
   YMD:function(key, value){
      if (this[key] instanceof Date){
         return this[key].getFullYear()
          +"-"+ ("00"+(this[key].getMonth()+ 1)).slice(-2) 
          +"-"+ ("00"+(this[key].getDate())).slice(-2);
      }
      return value;
   },
   YMD_slash:function(key, value){
      if (this[key] instanceof Date){
         return this[key].getFullYear() 
         +"/"+ ("00"+(this[key].getMonth()+ 1)).slice(-2) 
         +"/"+ ("00"+(this[key].getDate())).slice(-2);
      }
      return value;
   },
   YYYYMMDD:function(key, value){
      if (this[key] instanceof Date){
         return this[key].getFullYear()
          + ("00"+(this[key].getMonth()+ 1)).slice(-2)
          + ("00"+(this[key].getDate())).slice(-2);
      }
      return value;
   },
   YMDHMS:function(key, value){
      if (this[key] instanceof Date){
         return this[key].getFullYear() 
         +"-"+ ("00"+(this[key].getMonth()+ 1)).slice(-2)
         +"-"+ ("00"+(this[key].getDate())).slice(-2)
         +" "+ ("00"+(this[key].getHours())).slice(-2)
         +":"+ ("00"+(this[key].getMinutes())).slice(-2)
         +":"+ ("00"+(this[key].getSeconds())).slice(-2);
      }
      return value;
   },
   YMDHMSL:function(key, value){
      if (this[key] instanceof Date){
         return this[key].getFullYear() 
         +"-"+ ("00"+(this[key].getMonth()+ 1)).slice(-2) 
         +"-"+ ("00"+(this[key].getDate())).slice(-2)
         +" "+ ("00"+(this[key].getHours())).slice(-2)
         +":"+ ("00"+(this[key].getMinutes())).slice(-2)
         +":"+ ("00"+(this[key].getSeconds())).slice(-2) 
         + "." ("000"+(this[key].getMilliseconds())).slice(-3);
      }
      return value;
   },
   parseYMD:function(str){
      return new Date(str + " 00:00:00");
   },
   parseYYYYMMDD:function(str){
      return new Date(str.slice(0,4)
            +"-"+ str.slice(4, 6)
            +"-"+ str.slice(-2)
            + " 00:00:00");
   },
   parseYMDHMSL:function(str){
      var d =  new Date(str.slice(0,19));
      d.setMilliseconds(str.slice(-3));
      return d;
   }
};

長いので、これを1つのJSソースして使い回せるだろう。
テストサンプルは、、、

var dt = new Date();
var data = { a:dt, "b":"test" };

JsonDate.UTC テスト

var txt = JSON.stringify(data, JsonDate.UTC, ' ');
var obj = JSON.parse(txt, function(key, value){
   if (key === 'a'){
      return new Date(value);
   }
   return value;
});

JsonDate.YMD テスト

var txt = JSON.stringify(data, JsonDate.YMD, ' ');
var obj = JSON.parse(txt, function(key, value){
   if (key === 'a'){
      return JsonDate.parseYMD(value);
   }
   return value;
});

JsonDate.YMD_slash テスト

var txt = JSON.stringify(data, JsonDate.YMD_slash, ' ');
var obj = JSON.parse(txt, function(key, value){
   if (key === 'a'){
      return JsonDate.parseYMD(value);
   }
   return value;
});

JsonDate.YYYYMMDD テスト

var txt = JSON.stringify(data, JsonDate.YYYYMMDD, ' ');
var obj = JSON.parse(txt, function(key, value){
   if (key === 'a' && value){
      return JsonDate.parseYYYYMMDD(value);
   }
   return value;
});

JsonDate.YMDHMS テスト

var txt = JSON.stringify(data, JsonDate.YMDHMS, ' ');
var obj = JSON.parse(txt, function(key, value){
   if (key === 'a' && value){
      return new Date(value);
   }
   return value;
});

JsonDate.YMDHMSL テスト

var txt = JSON.stringify(data, JsonDate.YMDHMSL, ' ');
var obj = JSON.parse(txt, function(key, value){
  if (key === 'a' && value){
     return JsonDate.parseYMDHMSL(value);
  }
  return value;
});

しかし、JavaScript で日付の処理と言えば、
やはり Moment.js | Home である。
この場面では、parse ぐらいしか思いつかないが、、
moment.js を使えば、JsonDate.YYYYMMDD と JsonDate.YMDHMSL の parse は、
関数は書く必要なく

var txt = JSON.stringify(data, JsonDate.YYYYMMDD, ' ');
var obj = JSON.parse(txt, function(key, value){
   if (key === 'a' && value){
      return moment(str, "YYYYMMDD").toDate();
   }
   return value;
});
var txt = JSON.stringify(data, JsonDate.YMDHMSL, ' ');
var obj = JSON.parse(txt, function(key, value){
  if (key === 'a' && value){
     return moment(str, "YYYY-MM-DD HH:mm:ss.SSS").toDate();
  }
  return value;
});

とも書けるが、momentインスタンス生成する分の処理時間はかかるので
moment.js を馬鹿の1つ覚えのように使うのもどうなのかと。。。

jsTree の types プラグインの li_attr と a_attr の使い方が解りにくい

https://www.jstree.com/api/#/?f=$.jstree.defaults.types
types プラグインの li_attr と a_attr は、HTMLの li タグと a タグに追加する属性ということぐらいしか
書いてなくて、、どう書けば良いか?書いたらどうなるか?がどうも見えてこない。
結構問い合わせ多いみたいだ。。↓↓↓
how to use of li_attr · Issue #575 · vakata/jstree · GitHub

で、結局、
https://groups.google.com/forum/#!searchin/jstree/li_attr%7Csort:date/jstree/3CTkP1EXaZc/RtoQ4sDvAAAJ
グループの問い合わせ履歴を探すことに。。

例えば li_attr は、以下のように使う。。。

"plugins":[ "types" ],
"types" : {
   "default":{
      "icon" : "jstree-folder",
      "li_attr" : { "style" : "color:red" }
   }
},

これで、デフォルトの ノードは、style 属性、color: red; が追加されて文字色が赤になる。

表示するノード毎に、

"li_attr" : { "data-hoge": function(data){  ...   return ... ; }  } 

のようなことができると良いのだけど、無理みたいで残念

a_attr に使用価値があるのか疑問でもある。

jsTree の types プラグインを使う

https://www.jstree.com/api/#/?f=$.jstree.defaults.types

これを使えば、コンテキスメニューにおける _disabled の判定も、
以前書いた jsTree のコンテキストメニューをカスタマイズ - Oboe吹きプログラマの黙示録 も、get_node した icon を問い合わせるのでなく
type を問い合わせることができてこの方が better である。
プラグインの指定と、 types の定義、

"plugins":[ "contextmenu", "types" ],
"types" : {
   "default":{
      "icon" : "jstree-folder"
    },
   "leaf":{
        "icon" : "jstree-leaf"
    }
},

コンテキストメニューの定義は、こうなる。

"contextmenu":{
   "items":function($node){
      return {
         "createFolder":{
            "separator_before": false,
            "separator_after": false,
            "icon": "contextmenu-icon folder-plus",
            "label": "新規フォルダ作成",
            "_disabled": function(data){
               return $.jstree.reference(data.reference)
                      .get_node(data.reference).type != "default";
            },
            "action": function(data){
               var inst = $.jstree.reference(data.reference),
                   obj = inst.get_node(data.reference);
               inst.create_node(obj, { text:'New Folder' }, "last", function(new_node){
                  try{
                     inst.edit(new_node);
                  }catch(ex){
                     setTimeout(function(){ inst.edit(new_node); },0);
                  }
               });
            }
         },
         "createFile":{
            "separator_before": false,
            "separator_after": false,
            "icon": "contextmenu-icon file-plus",
            "label": "新規ファイル作成",
            "_disabled": function(data){
               return $.jstree.reference(data.reference)
                      .get_node(data.reference).type != "default";
            },
            "action": function(data){
               var inst = $.jstree.reference(data.reference),
                   obj = inst.get_node(data.reference);
               inst.create_node(obj, { text:'New File', 'type':'leaf' }, "last", function(new_node){
                  try{
                     inst.edit(new_node);
                  }catch(ex){
                     setTimeout(function(){ inst.edit(new_node); },0);
                  }
               });
            }
         },
         "rename":{
            "separator_before": true,
            "separator_after": false,
            "icon": "contextmenu-icon rename-icon",
            "label": "名称の変更",
            "_disabled": false,
            "action": function(data){
               var inst = $.jstree.reference(data.reference),
               obj = inst.get_node(data.reference);
               inst.edit(obj);
            }
         },
         "remove":{
            "separator_before": false,
            "separator_after": false,
            "icon": "contextmenu-icon delete-icon",
            "label": "削除",
            "_disabled": function(data){
               return $.jstree.reference(data.reference)
                      .get_node(data.reference).parent == "#";
            },
            "action": function(data){
               var inst = $.jstree.reference(data.reference),
                   obj = inst.get_node(data.reference);
               if (inst.is_selected(obj)){
                  inst.delete_node(inst.get_selected());
               }else{
                  inst.delete_node(obj);
               }
            }
         },
         "cut":{
            "separator_before": true,
            "separator_after": false,
            "icon": "contextmenu-icon cut-icon",
            "label": "切り取り",
            "_disabled": function(data){
               return $.jstree.reference(data.reference)
                      .get_node(data.reference).parent == "#";
            },
            "action": function(data){
               var inst = $.jstree.reference(data.reference), 
                   obj = inst.get_node(data.reference);
               if (inst.is_selected(obj)){
                  inst.cut(inst.get_top_selected());
               }else{
                  inst.cut(obj);
               }
            }
         },
         "copy":{
            "separator_before": false,
            "separator_after": false,
            "icon": "contextmenu-icon copy-icon",
            "label": "コピー",
            "_disabled": function(data){
               return $.jstree.reference(data.reference)
                      .get_node(data.reference).parent == "#";
            },
            "action": function(data){
               var inst = $.jstree.reference(data.reference),
                   obj = inst.get_node(data.reference);
               if (inst.is_selected(obj)){
                  inst.copy(inst.get_top_selected());
               }else{
                  inst.copy(obj);
               }
            }
         },
         "paste":{
            "separator_before": false,
            "separator_after": false,
            "icon": "contextmenu-icon paste-icon",
            "label": "貼り付け",
            "_disabled": function(data){
               if ($.jstree.reference(data.reference).get_node(data.reference).type != "default")
                  return true;
               return !$.jstree.reference(data.reference).can_paste();
            },
            "action": function(data){
               var inst = $.jstree.reference(data.reference),
                   obj = inst.get_node(data.reference);
               inst.paste(obj);
            }
         }
      };
   }
}

jsTree で下のツリー階層を開く時にもAJAX通信で読み込む方法

jsTree配布デモを見ても、ツリー全体JSONAJAXで読み込む方法はあるが、
静的に書いたJSON から下の階層を開く時や、AJAX読込み後さらに下の階層を読み込む方法が、
なかなか解らなかった。massload とかプラグインあるみたいだが、どうもできない。。。
f:id:posturan:20180929112809j:plain
クリックしたら
f:id:posturan:20180929112848j:plain
読込み終わったら自動的に、、
f:id:posturan:20180929112937j:plain
ということがしたい。巨大なツリーやあるノードにぶら下がる下が大量に子が存在すると、
これができない場合、ツリーデータ全てを読込みサーバから送信が終わるまで表示できないなんて
とてもつらいだろう。。
でもなんとか、AJAX読込み後さらに下の階層を読み込む方法が判った。
後から読み込ませるノードの id に任意のキーを設定し、初期状態を "state": "closed"
子ノードがあることだけを示すように、"children":[ ] を書かないで、"children": true を書く。
AJAX読込みの url は、url : function(node){ } を書いて、対象ノードIDで JSON取得先を変えて
ツリー全体か、設定した任意のキーに沿った JSON を取得するようなURL、
どちらかを返すようにする。

上の画像のサンプル
ツリー全体のJSONデータ (root.json

[
   {
      "id":1, "icon":"jstree-folder", "text":"Root 1", "title":"This is Root 1",
      "children":[{
         "id":2, "icon":"jstree-file", "text":"Child 2", "title":"This is Child 2"
      }]
   },{
       "id" : "submore",
       "icon": "jstree-folder",
        "text": "Root 2", "title":"open more ...",
        "state": "closed",
        "children": true
   }
]

↑"id" : "submore" のノードを開いた時の JSONデータ(sub.json

[{
   "id":21, "icon":"jstree-file", "text":"A001", "title":"This is A001"
},{
   "id":22, "icon":"jstree-file", "text":"A002", "title":"This is A002"
},{
   "id":23, "icon":"jstree-file", "text":"A003", "title":"This is A003"
},{
   "id":24, "icon":"jstree-file", "text":"A004", "title":"This is A004"
}]

ブラウザで表示させる jQuery

$.jstree.defaults.core.themes.variant = "large";
$.jstree.defaults.core.themes.responsive = true;

$('#tree').jstree({
   "core": {
      "multiple": false,
      "data": {
         "url" : function(node){
            var url = "/mycontext/treeview?req=#";
            if (node.id === "submore") url = "/mycontext/treeview?req=sub";
            return url;
         },
         "dataType" : "json"
      }
   }
}).on("hover_node.jstree", function(e, data){
   $("#"+data.node.id).prop('title', data.node.original.title);
});

サーバ側、別に Wicket でなくても要求に従って上のJSONを送るのが振り分けさえ
できてればよいのですが、参考として。。
Wicket での例です。
URLと WebPage クラスの紐付け、WebApplication の init でマウント

mountPage("/treeview", TreeAjaxJsonRes.class);

WebPage (TreeAjaxJsonRes) で

 getRequestCycle().getRequest().getQueryParameters().getParameterValue("req").toString();

でパラメータ受け取り

File file = // URLパラメータ"req" の結果で、root.json か sub.json のどちらかを読み込むファイル

getRequestCycle().scheduleRequestHandlerAfterCurrent(
   cycle->{
      try(InputStream in = new FileInputStream(file); ByteArrayOutputStream out = new ByteArrayOutputStream()){
         in.transferTo(out);
         text = out.toString();
         cycle.getResponse().getOutputStream().write(text.getBytes());
      }catch(IOException ex){
         logger.error(ex.getMessage(), ex);
      }
   }
);

ディクショナリの get関数、取得できないとき

Python 初心者向け。。。
ディクショナリの get関数でキーが存在しない時は、第2引数指定すること。
例えば、mongodb など検索して取得したデータ=ディクショナリで
以下のように、if 文ネストを深く書くのは避けたい。。。

{ "api": { "moneyrondaling":{ "checked": true }  } }

というデータがあったとする。つまり、、

data = { "api": {  "moneyrondaling":{ "checked": True }  } }

if 文ネストはみっともない

chksts = 0
if 'api' in datadb:
    if 'moneyrondaling' in data['api']:
        if 'checked' in data['api']['moneyrondaling']:
            chksts = 1 if data['api']['moneyrondaling']['checked'] else 0
        else:
            chksts = 0

get の第2引数に、空ディクショナリを指定する。

chksts = 1 if data.get('api', {}).get('moneyrondaling', {}).get('checked', False) else 0

jQuery3 から load関数は注意

jQuery 3から、画面ロード時の処理を

$(function(){
     $(window).on("load", function(){
          // TODO
      });
});

と書いたら動かない。もともと .load() や .unload() は、jQuery 3 のRC版が出た時の changelog
もう無くなると書いてある。
https://blog.jquery.com/2016/05/20/jquery-3-0-release-candidate-released/

$(function(){ } の外に書けば動作する。

$(function(){
      // 
});

$(window).on("load", function(){
        // TODO
});

でも、 $(window).on("resize", function(){ } は、$(function(){ } の中に書いていい。。。