diff src/kaigo/Nk/nk.go @ 41:34a474fb83c3

add perori/porori/nk.
author pyon@macmini
date Wed, 04 Mar 2020 23:46:59 +0900
parents
children c58172a59534
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/kaigo/Nk/nk.go	Wed Mar 04 23:46:59 2020 +0900
@@ -0,0 +1,795 @@
+/*
+ nk.go: Nintei Kekka
+
+ Last Change: 2020-01-24 金 09:18:04.
+*/
+
+package main
+
+/*
+#cgo LDFLAGS: -L. -lxdwapi -static
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <io.h>
+#include <windows.h>
+#include <xdw_api.h>
+#include <xdwapian.h>
+
+#define MAXCOL  1024
+#define MAXLINE 9999
+#define BLOCKSZ  128
+
+int xdwpages(const char* file) {
+	char in_path[_MAX_PATH];
+    _fullpath(in_path, file, _MAX_PATH);
+
+	XDW_DOCUMENT_HANDLE h = NULL; // 文書ハンドルを開く
+	XDW_OPEN_MODE_EX mode = {sizeof(XDW_OPEN_MODE_EX), XDW_OPEN_READONLY, XDW_AUTH_NODIALOGUE};
+	if (XDW_OpenDocumentHandle(in_path, &h, (XDW_OPEN_MODE*)&mode)) {
+		printf("Error: cannot open %s\n", file);
+		return -1;
+	}
+
+	XDW_DOCUMENT_INFO info = {sizeof(XDW_DOCUMENT_INFO), 0, 0, 0}; // 総ページ数を得る
+	XDW_GetDocumentInformation(h, &info);
+    int nPage = info.nPages;
+
+	XDW_CloseDocumentHandle(h, NULL); // 文書ハンドルを閉じる
+
+    return nPage;
+}
+
+char* xdw2txt(const char* file) {
+	char in_path[_MAX_PATH];
+    _fullpath(in_path, file, _MAX_PATH);
+
+	XDW_DOCUMENT_HANDLE h = NULL; // 文書ハンドルを開く
+	XDW_OPEN_MODE_EX mode = {sizeof(XDW_OPEN_MODE_EX), XDW_OPEN_READONLY, XDW_AUTH_NODIALOGUE};
+	if (XDW_OpenDocumentHandle(in_path, &h, (XDW_OPEN_MODE*)&mode)) {
+		printf("Error: cannot open %s\n", file);
+		return NULL;
+	}
+
+	XDW_DOCUMENT_INFO info = {sizeof(XDW_DOCUMENT_INFO), 0, 0, 0}; // 総ページ数を得る
+	XDW_GetDocumentInformation(h, &info);
+    int nPage = info.nPages;
+
+    // メイン処理
+    char *lpszvalue, *all_lpszvalue;
+	long datasize[9999];
+    for (int i=1; i<=nPage; i++) {
+		datasize[i] = XDW_GetPageTextToMemory(h, i, NULL, 0, NULL);
+		datasize[0] += datasize[i];
+    }
+	datasize[0] += nPage - 1;	// for "\n"
+	all_lpszvalue = (char*)malloc(sizeof(char)*datasize[0]);
+	all_lpszvalue[0] = '\0';
+    for (int i=1; i<=nPage; i++) {
+		if (i<nPage) datasize[i]++;	// for "\n"
+        lpszvalue = (char*)malloc(sizeof(char)*(datasize[i]));
+        XDW_GetPageTextToMemory(h, i, lpszvalue, datasize[i], NULL);
+		strcat(all_lpszvalue, lpszvalue);
+		if (i<nPage) strcat(all_lpszvalue, "\n");
+        free(lpszvalue);
+    }
+
+	XDW_CloseDocumentHandle(h, NULL); // 文書ハンドルを閉じる
+	return all_lpszvalue;
+}
+
+int xdwpush(const char* file1, const char* file2) {
+	char in_path[_MAX_PATH], add_path[_MAX_PATH];
+	_fullpath(in_path,  file1, _MAX_PATH);
+	_fullpath(add_path, file2, _MAX_PATH);
+
+	XDW_DOCUMENT_HANDLE h = NULL;
+	XDW_OPEN_MODE_EX mode = {sizeof(XDW_OPEN_MODE_EX), XDW_OPEN_UPDATE, XDW_AUTH_NODIALOGUE};
+
+	int api_result = XDW_OpenDocumentHandle(in_path, &h, (XDW_OPEN_MODE*)&mode);
+	if (api_result < 0) return api_result;
+
+	XDW_DOCUMENT_INFO info = {sizeof(XDW_DOCUMENT_INFO), 0, 0, 0};
+	XDW_GetDocumentInformation(h, &info);
+	int last_page = info.nPages;
+
+	api_result = XDW_InsertDocument(h, last_page+1, add_path, NULL);
+	if (api_result < 0) api_result;
+
+	XDW_SaveDocument(h, NULL);
+	XDW_CloseDocumentHandle(h, NULL);
+
+	return (api_result >= 0);
+}
+
+int xdwaddatn(const char* file, char* atnlist) {
+	char in_path[_MAX_PATH];
+	_fullpath(in_path, file, _MAX_PATH);
+
+	XDW_DOCUMENT_HANDLE h = NULL;
+	XDW_OPEN_MODE_EX mode = {sizeof(XDW_OPEN_MODE_EX), XDW_OPEN_UPDATE, XDW_AUTH_NODIALOGUE};
+
+	int api_result = XDW_OpenDocumentHandle(in_path, &h, (XDW_OPEN_MODE*)&mode);
+	if (api_result < 0) return api_result;
+
+	XDW_DOCUMENT_INFO info = {sizeof(XDW_DOCUMENT_INFO), 0, 0, 0};
+	XDW_GetDocumentInformation(h, &info);
+
+	char *str, *token;
+	char *saveptr1, *saveptr2;
+	int x, y, sz, tr;
+	char *s;
+
+	for (str = atnlist; ; str = NULL) {
+		token = strtok_r(str, ":", &saveptr1);
+		if (token == NULL) break;
+		//printf("%s\n", token);
+
+		// atn.X, atn.Y, atn.Sz, tr, atn.Txt
+		x  = atoi(strtok_r(token, ",", &saveptr2));
+		y  = atoi(strtok_r(NULL , ",", &saveptr2));
+		sz = atoi(strtok_r(NULL,  ",", &saveptr2));
+		tr = atoi(strtok_r(NULL,  ",", &saveptr2));
+		s  =      strtok_r(NULL,  ",", &saveptr2);
+		//printf("x=%d y=%d txt=%s sz=%d tr=%d\n", x, y, s, sz, tr);	
+
+		for (int i = 0; i < info.nPages; i++ ) {
+			XDW_ANNOTATION_HANDLE annoation;
+			int api_result = XDW_AddAnnotation(h, XDW_AID_TEXT, i + 1, x, y, NULL, &annoation, NULL);
+
+			if (api_result < 0) return api_result;
+
+			api_result = XDW_SetAnnotationAttribute(h, annoation, XDW_ATN_Text, XDW_ATYPE_STRING, s, 0, NULL);
+			api_result = XDW_SetAnnotationAttribute(h, annoation, XDW_ATN_FontSize, XDW_ATYPE_INT, (char*)&sz, 0, NULL);
+
+			if (tr) {
+				int color = XDW_COLOR_NONE;
+				api_result = XDW_SetAnnotationAttribute(h, annoation, XDW_ATN_BackColor, XDW_ATYPE_INT, (char*)&color, 0, NULL);
+			}
+		}
+	}
+
+	XDW_SaveDocument(h, NULL);
+	XDW_CloseDocumentHandle(h, NULL);
+
+	return 0;
+}
+
+int xdwsort(const char* file, const char* sorted, const char* order, const char* workdir, const char* prefix) {
+	char buf[MAXCOL];
+
+	// メモリ確保
+	char *sl = (char*)malloc(MAXLINE * sizeof(char) * MAXCOL);
+	if (sl == NULL) return -1;
+
+	int slN = 0;
+	char *p;
+	p = strtok(order, ":");
+	strncpy(&sl[slN * MAXCOL], p, MAXCOL);
+	slN++;
+
+	while (p = strtok(NULL, ":")) {
+		strncpy(&sl[slN * MAXCOL], p, MAXCOL);
+		slN++;
+	}
+
+	//for (int j = 0; j < slN; j++) printf("%d : %s\n", j, &sl[j * MAXCOL]);
+	//return 0;
+
+	// 重み付け = 並び順
+	char in_path[_MAX_PATH];
+	_fullpath(in_path, file, _MAX_PATH);
+
+	XDW_DOCUMENT_HANDLE h = NULL;
+	XDW_OPEN_MODE_EX mode = {sizeof(XDW_OPEN_MODE_EX), XDW_OPEN_READONLY, XDW_AUTH_NODIALOGUE};
+
+	int api_result = XDW_OpenDocumentHandle(in_path, &h, (XDW_OPEN_MODE*)&mode);
+	if (api_result < 0) return api_result;
+
+	XDW_DOCUMENT_INFO info = {sizeof(XDW_DOCUMENT_INFO), 0, 0, 0};
+	XDW_GetDocumentInformation(h, &info);
+	int last_page = info.nPages;
+
+	int *table = (int*)malloc(sizeof(int) * last_page);
+	for (int p = 0; p < last_page; p++) *(table + p) = 9999;
+
+	int index = 0;
+	XDW_FOUND_HANDLE pFoundHandle = NULL;
+	for (int i = 0; i < slN; i++) {
+		for (int p = 0; p < last_page; p++) {
+			if (*(table + p) != 9999) continue;
+
+			api_result = XDW_FindTextInPage(h, p + 1, &sl[i * MAXCOL], NULL, &pFoundHandle, NULL);
+			if (api_result < 0) return api_result;
+
+			if (pFoundHandle != NULL) {
+				*(table + p) = ++index;
+				pFoundHandle = NULL;
+			}
+		}
+	}
+	free(sl);
+
+	for (int p = 0; p < last_page; p++) {
+		if (*(table + p) == 9999) *(table + p) = ++index;
+
+		sprintf(buf, "%s/%s_%04d.xdw", workdir, prefix, *(table + p));
+		_fullpath(in_path, buf, _MAX_PATH);
+
+		api_result = XDW_GetPage(h, p + 1, in_path, NULL);
+		if (api_result < 0) return api_result;
+	}
+	free(table);
+	XDW_CloseDocumentHandle(h, NULL);
+
+	// ブロック処理
+	char *blk_path = (char*)malloc(BLOCKSZ * sizeof(char) * _MAX_PATH);
+	const char **blk_path_addr = (const char**)malloc((last_page / BLOCKSZ + 1) * sizeof(char*) * _MAX_PATH);
+
+	int bn = 0;
+	for (int p = 0, m = 0; p < last_page; p++) {
+		m = p % BLOCKSZ;
+
+		if (m == 0 && p > 0) {
+			sprintf(buf, "%s/%s_b%04d.xdw", workdir, prefix, ++bn);
+			_fullpath(in_path, buf, _MAX_PATH);
+
+			api_result = XDW_MergeXdwFiles(blk_path_addr, BLOCKSZ, in_path, NULL);
+			if (api_result < 0) return api_result;
+		} 
+
+		sprintf(buf, "%s/%s_%04d.xdw", workdir, prefix, p + 1);
+		_fullpath(in_path, buf, _MAX_PATH);
+
+		strncpy(&blk_path[m * _MAX_PATH], in_path, _MAX_PATH);
+
+		blk_path_addr[m] = &blk_path[m * _MAX_PATH];
+	}
+
+	sprintf(buf, "%s/%s_b%04d.xdw", workdir, prefix, ++bn);
+	_fullpath(in_path, buf, _MAX_PATH);
+
+	int mod = last_page % BLOCKSZ;
+	if (mod == 0) mod = BLOCKSZ; 
+
+	api_result = XDW_MergeXdwFiles(blk_path_addr, mod, in_path, NULL);
+	if (api_result < 0) return api_result;
+
+	for (int b = 0; b < bn; b++) {
+		sprintf(buf, "%s/%s_b%04d.xdw", workdir, prefix, b + 1);
+
+		_fullpath(in_path, buf, _MAX_PATH);
+		strncpy(&blk_path[b * _MAX_PATH], in_path, _MAX_PATH);
+
+		blk_path_addr[b] = &blk_path[b * _MAX_PATH];
+	}
+
+	_fullpath(in_path, sorted, _MAX_PATH);
+
+	api_result = XDW_MergeXdwFiles(blk_path_addr, bn, in_path, NULL);
+	if (api_result < 0) return api_result;
+
+	free(blk_path);
+	free(blk_path_addr);
+
+    return 0;
+}
+
+int xdw2pdf(const char* xdwfile, const char* pdffile) {
+	char in_path[_MAX_PATH], out_path[_MAX_PATH];
+	_fullpath(in_path,  xdwfile, _MAX_PATH);
+	_fullpath(out_path, pdffile, _MAX_PATH);
+
+	remove(out_path);
+	int api_result = 0;
+
+	XDW_DOCUMENT_HANDLE h = NULL;
+	XDW_OPEN_MODE_EX mode = {sizeof(XDW_OPEN_MODE_EX), XDW_OPEN_READONLY, XDW_AUTH_NODIALOGUE};
+
+	api_result = XDW_OpenDocumentHandle(in_path, &h, (XDW_OPEN_MODE*)&mode);
+	if (api_result < 0) return api_result;
+
+	XDW_DOCUMENT_INFO info = {sizeof(XDW_DOCUMENT_INFO), 0, 0, 0};
+	XDW_GetDocumentInformation(h, &info);
+
+	XDW_IMAGE_OPTION_PDF pdf = { 
+		sizeof(XDW_IMAGE_OPTION_PDF),
+		XDW_COMPRESS_MRC_NORMAL,
+		XDW_CONVERT_MRC_OS,
+		info.nPages
+	};  
+
+	XDW_IMAGE_OPTION_EX ex = {
+		sizeof(XDW_IMAGE_OPTION_EX),
+		600, // dpi
+		XDW_IMAGE_MONO_HIGHQUALITY,
+		XDW_IMAGE_PDF,
+		&pdf
+	};
+
+	api_result = XDW_ConvertPageToImageFile(h, 1, out_path, (XDW_IMAGE_OPTION*)&ex);
+
+	XDW_SaveDocument(h, NULL);
+	XDW_CloseDocumentHandle(h, NULL);
+
+	return 0;
+}
+
+int xdwopt(const char* in, const char* out) {
+	char in_path[_MAX_PATH], out_path[_MAX_PATH];
+    _fullpath(in_path,  in,  _MAX_PATH);
+    _fullpath(out_path, out, _MAX_PATH);
+
+	int api_result = XDW_OptimizeDocument(in_path, out_path, NULL);
+	return (api_result >= 0);
+}
+*/
+import "C"
+
+import (
+	"bufio"
+    "encoding/json"
+    "fmt"
+	"flag"
+    "io/ioutil"
+    "log"
+    "os"
+    "os/exec"
+    "path/filepath"
+	"regexp"
+	"sort"
+    "strings"
+	"time"
+
+	"golang.org/x/text/encoding/japanese"
+	"golang.org/x/text/transform"
+)
+
+var (
+    ver = "0.1"
+
+	flg_hb int
+	flg_time bool
+	flg_log bool
+	flg_debug bool
+	flg_clean bool
+
+	confjson = "nk.json"
+	logfile = "nk.log"
+	hhscsv = "hhsdb.csv"
+	tmpdir string
+	tmpprefix = "nktemp_"
+	inputdir = "input"
+	outputdir = "output"
+	outcsv = "out.csv"
+
+	start time.Time
+
+    hb string // 発番
+    fw, hs, kt = "", "", "kttemp.xdw" // 負担割合証, 被保険者証, 結果通知
+
+    step, totalstep = 1, 13
+)
+
+type Annotation struct {
+	X, Y int
+	Sz int
+	Tr bool
+	Txt string
+}
+
+type Config struct {
+	Atns []Annotation
+	Kyotaku []string
+}
+
+func (c *Config) ProcessTemplate() {
+	today := time.Now().Format("令和 r 年 1 月 2 日")
+	y := time.Now().Year() - 2018
+	if y == 1 {
+		today = strings.ReplaceAll(today, "r", "元")
+	} else {
+		today = strings.ReplaceAll(today, "r", fmt.Sprintf("%d", y))
+	}
+
+	var atns []Annotation
+	for _, atn := range c.Atns {
+		s := atn.Txt
+		if atn.Txt == "date" {
+			s = today
+		}
+		if strings.ContainsAny(atn.Txt, "hb") {
+			s = strings.ReplaceAll(atn.Txt, "hb", hb)
+		}
+		atns = append(atns, Annotation{atn.X, atn.Y, atn.Sz, atn.Tr, s})
+	}
+	c.Atns = atns
+}
+
+func (c *Config) KyotakuList() []string {
+	return c.Kyotaku
+}
+
+func (c *Config) AnnotationList() (al []string) {
+	for _, atn := range c.Atns {
+		tr := 0
+		if atn.Tr {
+			tr = 1
+		}
+		s := fmt.Sprintf("%d,%d,%d,%d,%s", atn.X, atn.Y, atn.Sz, tr, atn.Txt)
+		al = append(al, s)
+	}
+	return
+}
+
+func init() {
+	/* INITIALIZE FLAGS */
+	flag.IntVar(&flg_hb, "b", 0, "set hatsuban")
+	flag.BoolVar(&flg_time, "t", false, "print time")
+	flag.BoolVar(&flg_log, "l", false, "save log")
+	flag.BoolVar(&flg_debug, "d", false, "print data for debug")
+	flag.BoolVar(&flg_clean, "c", false, "clean temporary directory & exit")
+
+    /* CREAN OUTPUT-FILE & TEMP-FILE */
+    if err := os.RemoveAll(outputdir); err != nil {
+        log.Fatal(err)
+    }
+    os.Remove(logfile)
+
+	/* PREPARATE OUTPUT-DIR & TEMP-DIR */
+	if err := os.Mkdir(outputdir, 0755); err != nil {
+		log.Fatal(err)
+	}
+
+	var err error
+	tmpdir, err = ioutil.TempDir(".", tmpprefix)
+	if err != nil {
+		log.Fatal(err)
+	}
+	logfile = filepath.Join(tmpdir, logfile)
+	kt = filepath.Join(tmpdir, kt)
+}
+
+func main() {
+	flag.Parse()
+
+    /* CLEAN TEMPORARY DIRECTORY */
+	if flg_clean {
+		files, err := ioutil.ReadDir(".")
+		if err != nil {
+			log.Fatal(err)
+		}
+		for _, file := range files {
+			if strings.HasPrefix(file.Name(), tmpprefix) {
+				if err := os.RemoveAll(file.Name()); err != nil {
+					log.Fatal(err)
+				}
+			}
+		}
+		os.Exit(0)
+	}
+
+    /* PRINT HEADER */
+    fmt.Println("=======================================")
+    fmt.Println(" 被保険者証と結果通知と負担割合証を... ")
+    fmt.Printf("         - nk [ver %s] -\n", ver)
+    fmt.Println("=======================================\n")
+
+    /* USER INPUT */
+	hb = fmt.Sprintf("%d", flg_hb)
+	if flg_hb == 0 {
+		fmt.Print("発番 > ")
+		fmt.Scan(&hb)
+	}
+
+	start = time.Now()
+
+    /* READ CONFIG FROM JSON */
+    print_step("設定読込み")
+	conf, err := read_conf(confjson)
+	if err != nil {
+		log.Fatal(err)
+	}
+	conf.ProcessTemplate()
+	step_start := print_time(start)
+
+    /* CHECK INPUT-FILE */
+    print_step("入力ファイルのチェック")
+    files, err := ioutil.ReadDir(inputdir)
+    if err != nil {
+        log.Fatal(err)
+    }
+
+    var kts []string
+    for _, file := range files {
+        if strings.HasSuffix(file.Name(), ".xdw") {
+            print_debug([]string{file.Name()})
+            switch file.Name()[0:8] {
+            case "KBPV016G":
+                fw = filepath.Join(inputdir, file.Name())
+            case "KBPG316G":
+                hs = filepath.Join(inputdir, file.Name())
+            case "KBPG206G", "KBPG706G":
+                f := filepath.Join(inputdir, file.Name())
+                kts = append(kts, f)
+            }
+        }
+    }
+
+    fmt.Println()
+    fmt.Printf(" 負担割合証ファイル = %s\n", fw)
+    fmt.Printf(" 被保険者証ファイル = %s\n", hs)
+    fmt.Print(" 結果通知ファイル   =")
+    for _, f := range kts {
+        fmt.Printf(" %s", f)
+    }
+    fmt.Println()
+    if fw == "" || hs == "" || len(kts) == 0 {
+        fmt.Fprintf(os.Stderr, "Input file is wrong.\n")
+        os.Exit(1)
+    }
+
+	bytes, err := ioutil.ReadFile(hhscsv)
+	if err != nil {
+		log.Fatal(err)
+	}
+	hash_hhs := make(map[string]string) // 被保険者氏名のハッシュ
+	r := strings.NewReader(string(bytes))
+	tr := transform.NewReader(r, japanese.ShiftJIS.NewDecoder())
+	buf := bufio.NewScanner(tr)
+	for buf.Scan() {
+		records := strings.Split(buf.Text(), ",")
+		hash_hhs[records[0]] = records[2]
+	}
+    fmt.Printf(" 被保険者ファイル   = %s\n", hhscsv)
+
+	step_start = print_time(step_start)
+
+    /* CONCATNATE INPUT-FILE */
+    print_step("結果通知ファイルの連結")
+	b, err := ioutil.ReadFile(kts[0])
+	if err != nil {
+		log.Fatal(err)
+	}
+	if err = ioutil.WriteFile(kt, b, 0644); err != nil {
+		log.Fatal(err)
+	}
+    if len(kts) > 1 {
+        pp := 0
+		fmt.Println()
+        for _, file := range kts {
+            p, _ := C.xdwpages(C.CString(file))
+            fmt.Printf(" %s\t= %d ページ\n", file, int(p))
+            pp += int(p)
+		}
+        fmt.Printf("   合計\t= %d ページ\n", pp)
+        for _, file := range kts[1:] {
+            C.xdwpush(C.CString(kt), C.CString(file))
+        }
+    }
+	step_start = print_time(step_start)
+
+    /* MAKE SORT-TABEL */
+    print_step("並び順の決定")
+
+	re_hhs := regexp.MustCompile(`05((2126)|(2159)|(4346))0[1238]\d{8}`)
+	re_kaigo := regexp.MustCompile(`要((介護)|(支援)).`)
+
+	hash_fw := make(map[string]int) // 負担割合証発行者のハッシュ
+	for _, t := range xdw2txt(fw) {
+		hash_fw[re_hhs.FindString(t)]++
+		print_debug([]string{re_hhs.FindString(t)})
+	}
+
+	kyotaku := conf.KyotakuList()
+
+	hash_kaigo := make(map[string]string)   // 被保険者証発行者の要介護度のハッシュ
+	hash_kyotaku := make(map[string]string) // 被保険者証発行者の居宅介護支援事業所のハッシュ
+	var sorttable []string
+	for _, t := range xdw2txt(hs) {
+		h := re_hhs.FindString(t)
+		hash_kaigo[h] = re_kaigo.FindString(t)
+		for  _, k := range kyotaku {
+			if strings.Contains(t, k) {
+				hash_kyotaku[h] = k
+			}
+		}
+		key := make_sort_key(hash_fw[h], re_kaigo.FindString(t), h)
+		s := fmt.Sprintf("%s#%d:%s:%s#%s", key, hash_fw[h], re_kaigo.FindString(t), hash_kyotaku[h], h)
+		sorttable = append(sorttable, s)
+	}
+	//sort.Sort(sort.Reverse(sort.StringSlice(sorttable)))
+	sort.Sort(sort.StringSlice(sorttable))
+	print_debug(sorttable)
+	step_start = print_time(step_start)
+
+    /* DO SORT */
+	order := ""
+	for _, s := range sorttable {
+		t := strings.Split(s, "#")
+		order += ":" + t[len(t)-1][6:]
+	}
+	order = strings.Replace(order, ":", "", 1)
+
+    print_step("被保険者証並び替え")
+	hs_sorted := filepath.Join(tmpdir, "hs.xdw")
+	C.xdwsort(C.CString(hs), C.CString(hs_sorted), C.CString(order), C.CString(tmpdir), C.CString("hs"))
+	step_start = print_time(step_start)
+
+    print_step("負担割合証並び替え")
+	fw_sorted := filepath.Join(tmpdir, "fw.xdw")
+	C.xdwsort(C.CString(fw), C.CString(fw_sorted), C.CString(order), C.CString(tmpdir), C.CString("fw"))
+	step_start = print_time(step_start)
+
+    print_step("結果通知並び替え")
+	kt_sorted := filepath.Join(tmpdir, "kt.xdw")
+	C.xdwsort(C.CString(kt), C.CString(kt_sorted), C.CString(order), C.CString(tmpdir), C.CString("kt"))
+	step_start = print_time(step_start)
+
+    /* ADD ANNOTATION */
+    print_step("発番印字")
+	al := strings.Join(conf.AnnotationList(), ":")
+	al, _, _ = transform.String(japanese.ShiftJIS.NewEncoder(), al)
+    C.xdwaddatn(C.CString(kt_sorted), C.CString(al))
+	step_start = print_time(step_start)
+
+    /* OPTIMIZE OUTPUT-FILE */
+    print_step("最適化")
+	hs_opt := filepath.Join(outputdir, "hs.xdw")
+    C.xdwopt(C.CString(hs_sorted), C.CString(hs_opt))
+	fw_opt := filepath.Join(outputdir, "fw.xdw")
+    C.xdwopt(C.CString(fw_sorted), C.CString(fw_opt))
+	kt_opt := filepath.Join(outputdir, "kt.xdw")
+    C.xdwopt(C.CString(kt_sorted), C.CString(kt_opt))
+	step_start = print_time(step_start)
+
+    /* OUTPUT CSV */
+    print_step("CSV出力")
+	outcsv = filepath.Join(outputdir, outcsv)
+	fcsv, err := os.OpenFile(outcsv, os.O_RDWR|os.O_CREATE, 0644)
+	if err != nil {
+		log.Fatal(err)
+	}
+	for i, s := range sorttable {
+		t := strings.Split(s, "#")
+		u := strings.ReplaceAll(t[1], ":", ",")
+		u, _, _ = transform.String(japanese.ShiftJIS.NewEncoder(), u)
+		c := t[2][0:6]
+		h := t[2][6:16]
+		n, _, _ := transform.String(japanese.ShiftJIS.NewEncoder(), hash_hhs[h])
+		// seq, city, hno, name, kaigo, fw, kyotaku
+		fmt.Fprintf(fcsv, "%04d,%s,%s,%s,%s\n", i + 1, c, h, n, u)
+	}
+	if err := fcsv.Close(); err != nil {
+		log.Fatal(err)
+	}
+	step_start = print_time(step_start)
+
+    /* PDF */
+    print_step("負担割合証割付PDF作成")
+	fwpdf := filepath.Join(tmpdir, "fw.pdf")
+	C.xdw2pdf(C.CString(fw_opt), C.CString(fwpdf)) // 2min
+	step_start = print_time(step_start)
+
+    print_step("負担割合証PDF割付")
+	fw4pdf := filepath.Join(outputdir, "fw4.pdf")
+	cmd := exec.Command("fw4.exe", fwpdf, fw4pdf, tmpdir)
+	if err := cmd.Run(); err != nil {
+		log.Fatal(err)
+	}
+	step_start = print_time(step_start)
+
+    print_step("終了")
+	step_start = print_time(step_start)
+
+	/* REMOVE TEMP-FILE */
+	if flg_log {
+		logfile2 := filepath.Join(".", strings.Replace(logfile, tmpdir, "", 1))
+		os.Link(logfile, logfile2)
+	}
+
+	if !flg_debug {
+		if err := os.RemoveAll(tmpdir); err != nil {
+			log.Fatal(err)
+		}
+	}
+}
+
+func make_sort_key(fw int, kaigo, h string) string {
+	key := fmt.Sprintf("%d:", 9 - fw)
+	if strings.HasPrefix(kaigo, "要支援") {
+		key += "1"
+	}
+	if strings.HasPrefix(kaigo, "要介護") {
+		key += "2"
+	}
+	switch {
+	case strings.HasSuffix(kaigo, "1"):
+		key += "1:"
+	case strings.HasSuffix(kaigo, "2"):
+		key += "2:"
+	case strings.HasSuffix(kaigo, "3"):
+		key += "3:"
+	case strings.HasSuffix(kaigo, "4"):
+		key += "4:"
+	case strings.HasSuffix(kaigo, "5"):
+		key += "5:"
+	default:
+		key += "00:"
+	}
+	return key + h
+}
+
+func xdw2txt(file string) (txt []string) {
+	s := C.GoString(C.xdw2txt(C.CString(file)))
+	r := strings.NewReader(s)
+	tr := transform.NewReader(r, japanese.ShiftJIS.NewDecoder())
+	buf := bufio.NewScanner(tr)
+	for buf.Scan() {
+		txt = append(txt, buf.Text())
+	}
+	return;
+}
+
+func read_conf(file string) (conf Config, err error) {
+    content, err := ioutil.ReadFile(file)
+    if err != nil {
+        log.Fatal(err)
+    }
+	err = json.Unmarshal(content, &conf)
+	return
+}
+
+func print_step(msg string) {
+	s := fmt.Sprintf("\n[%d/%d] %s\n", step, totalstep, msg)
+    step++
+	fmt.Print(s)
+	save_log(s)
+}
+
+func print_time(t time.Time) time.Time {
+	now := time.Now()
+	if !flg_time {
+		return now
+	}
+	elapsed := now.Sub(t)
+	total := now.Sub(start)
+	s := fmt.Sprintf("---- Elapsed: %v (total = %v) @ %02d:%02d\n", elapsed, total, now.Hour(), now.Minute())
+	fmt.Print(s)
+	save_log(s)
+	return now
+}
+
+func print_debug(msg []string) {
+	if !flg_debug {
+		return
+	}
+	s := ""
+	if len(msg) == 1 {
+		s = fmt.Sprintf("----- %s\n", msg)
+	}
+	for i, s := range msg {
+		s += fmt.Sprintf("%05d %s\n", i, s)
+	}
+	fmt.Print(s)
+	save_log(s)
+}
+
+func save_log(logtxt string) error {
+	if !flg_log {
+		return nil
+	}
+	f, err := os.OpenFile(logfile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+	if err != nil {
+		return err
+	}
+	if _, err := f.Write([]byte(logtxt)); err != nil {
+		f.Close()
+		return err
+	}
+	if err := f.Close(); err != nil {
+		return err
+	}
+	return nil
+}
+