view src/kaigo/Nk/nk.go @ 64:ad5c30ee5cf1

horori: add manual.
author pyon@macmini
date Fri, 25 Dec 2020 20:48:01 +0900
parents 17e042fc8a33
children
line wrap: on
line source

/*
 nk.go: Nintei Kekka

 Last Change: 2020-10-16 金 14:27:50.
*/

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 xdwhbaddatn(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 xdwerase(const char* in_file, const char* ktxt, const char* htxt) {
	int x = 20000;
	int y = 3685;
	int sz = 480;

	char in_path[_MAX_PATH];
	_fullpath(in_path, in_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);

    XDW_FOUND_HANDLE pFoundHandle = NULL;
	for (int i = 0; i < info.nPages; i++) {
		api_result = XDW_FindTextInPage(h, i + 1, htxt, NULL, &pFoundHandle, NULL);
		if (pFoundHandle == NULL) continue;

		api_result = XDW_FindTextInPage(h, i + 1, ktxt, NULL, &pFoundHandle, NULL);
		if (pFoundHandle == NULL) continue;

		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, "        ", 0, NULL);
		api_result = XDW_SetAnnotationAttribute(h, annoation, XDW_ATN_FontSize, XDW_ATYPE_INT, (char*)&sz, 0, NULL);
	}

	XDW_SaveDocument(h, NULL);
	XDW_CloseDocumentHandle(h, NULL);

	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.3"

	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, 14
)

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 HATSUBAN */
    print_step("発番印字")
	al := strings.Join(conf.AnnotationList(), ":")
	al, _, _ = transform.String(japanese.ShiftJIS.NewEncoder(), al)
    C.xdwhbaddatn(C.CString(kt_sorted), C.CString(al))
	step_start = print_time(step_start)

    /* ERASE HOUKATSU  */
    print_step("包括除去")
	ktxt, _, _ := transform.String(japanese.ShiftJIS.NewEncoder(), "要介護")
	htxt, _, _ := transform.String(japanese.ShiftJIS.NewEncoder(), "包括支援センター")
    C.xdwerase(C.CString(hs_sorted), C.CString(ktxt), C.CString(htxt))
	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
}