kzhr's diary

ad ponendum

「日本古典籍字形データセットをかんたんに分類してくれるPythonスクリプト」の結果を(手で)修正した量の統計を出すスクリプト

#!/usr/bin/perl

use 5.012;

use strict;
use warnings;
use utf8;

use Cwd;
use FindBin;

use Data::Dumper;

require "$FindBin::Bin/clgr.pl";

my $wd = Cwd::getcwd();
my @directories = clgr::scan_dir($wd);

say join ",", 'Mat', 'U+ID', 'Total', 'Err', 'Err%', 'Orig No C', 'Mod No C', 'Diff';

for my $dir (@directories) {
  my @path = clgr::gen_path($dir);
  my $csv = clgr::read_csv($dir);
  my $data = clgr::cluster($csv);

  my %stat = ();
  my $i = 0;
  my %c_no = ();

  for my $c (@$data) {
    $c_no{mod}{$i}++;
    for my $item (@$c){
      $stat{$i}{$item->[1]}++;
      $c_no{org}{$item->[1]}++;
    }
    $i++;
  }
  my @stat_keys = sort keys %stat;
  my $error_no = 0;
  my $total = 0;

  for my $stat_key (@stat_keys) {
    my $cluster_max = 0;
    my $cluster_total = 0;
    my @original_stat_keys = sort keys %{$stat{$stat_key}};
    for my $original_stat_key (@original_stat_keys) {
      $cluster_total += $stat{$stat_key}{$original_stat_key};
      $cluster_max = $stat{$stat_key}{$original_stat_key} > $cluster_max ? $stat{$stat_key}{$original_stat_key} : $cluster_max;
    }
    $total += $cluster_total;
    $error_no += $cluster_total - $cluster_max;
  }

  my $orig_no_c = scalar keys %{$c_no{org}};
  my $mod_no_c = scalar keys %{$c_no{mod}};
  # 'Mat', 'U+ID', 'Total', 'Err', 'Err%', 'Orig No C', 'Mod No C', 'Diff b/w Orig & Mod No C'
  say join ",", $path[-3], $path[-1], $total, $error_no, sprintf("%.2f", $error_no / $total * 100), $orig_no_c, $mod_no_c, abs($orig_no_c - $mod_no_c);
#  warn Dumper($data, \%stat);
#  last;
}

「日本古典籍字形データセットをかんたんに分類してくれるPythonスクリプト」の結果を修正してHTMLに再出力するスクリプト

#!/usr/bin/perl

use 5.012;

use strict;
use warnings;
use utf8;

use Cwd;
use FindBin;

use Data::Dumper;

require "$FindBin::Bin/clgr.pl";

my $now = time();

my $wd = Cwd::getcwd();
my @directories = clgr::scan_dir($wd);

for my $dir (@directories) {
    my @path = clgr::gen_path($dir);
    my $html_file = "$path[-3]/$path[-3]_mod.html";
    my $csv_file = "$path[-3]/$path[-3].csv";
    if (not -e $html_file or (stat $html_file)[9] < $now) {
        open my $html_fh, ">", $html_file or die $html_file;
        print $html_fh clgr::regen_html_header();
    }
    if (not -e $csv_file or (stat $csv_file)[9] < $now) {
        open my $csv_fh, ">", $csv_file or die $csv_file;
        print $csv_fh clgr::regen_csv_header(); 
    }

    my $data = clgr::read_csv($dir);
    my $cluster = clgr::cluster($data);
    my $html = clgr::regen_html($path[-1], "$wd/$path[-3]", $cluster);
    my $csv = clgr::regen_csv($path[-1], $path[-3], $cluster);
    open my $html_fh, ">>", $html_file or die $html_file;
    print $html_fh $html;
    open my $csv_fh, ">>", $csv_file or die $csv_file;
    print $csv_fh $csv;
}

clgr.pl

#!which perl

package clgr;

use strict;
use warnings;
use utf8;
use File::Spec;
use Image::Size qw/html_imgsize/;

my @kana = (
    'U+3042', 'U+3044', 'U+3046', 'U+3048', 'U+304A', 'U+304B',
    'U+304C', 'U+304D', 'U+304E', 'U+304F', 'U+3050', 'U+3051',
    'U+3052', 'U+3053', 'U+3054', 'U+3055', 'U+3056', 'U+3057',
    'U+3058', 'U+3059', 'U+305A', 'U+305B', 'U+305C', 'U+305D',
    'U+305E', 'U+305F', 'U+3060', 'U+3061', 'U+3062', 'U+3064',
    'U+3065', 'U+3066', 'U+3067', 'U+3068', 'U+3069', 'U+306A',
    'U+306B', 'U+306C', 'U+306D', 'U+306E', 'U+306F', 'U+3070',
    'U+3072', 'U+3073', 'U+3075', 'U+3076', 'U+3078', 'U+3079',
    'U+307B', 'U+307C', 'U+307E', 'U+307F', 'U+3080', 'U+3081',
    'U+3082', 'U+3084', 'U+3086', 'U+3088', 'U+3089', 'U+308A',
    'U+308B', 'U+308C', 'U+308D', 'U+308F', 'U+3090', 'U+3091',
    'U+3092', 'U+3093'
);

sub scan_dir {
    my $dir = shift;
    $dir .= '/' if $dir !~ m!/\z!ms;
    my @dirs = ();
    opendir(my $dh, $dir) or die $dir;
    while (my $rdir = readdir($dh)) {
        next if $rdir =~ /\A\.+\z/ms;
        if (-d $dir . $rdir) {
            if (grep {$_ eq $rdir} @kana) {
                push @dirs, $dir . $rdir;
            }
            else {
                my @subdir = scan_dir($dir . $rdir);
                push @dirs, @subdir;
            }
        }
    }
    return @dirs;
}

sub read_csv {
    my $dir = shift;
    my @csv = ();
    $dir =~ m!/(U[^/]+)\z!ms;
    my $file = $1 . ".csv";
    open my $fh, "$dir/$file" or die "$dir/$file";
    my $head = 0;
    while(my $line = <$fh>) {
        next if not $head++;
        chomp $line;
        $line =~ tr/"//d;
        warn $& if $line =~ /[^\.,\/\+\_A-Za-z0-9]/;
        my @split = split ',', $line;
        if (not $split[2] and $split[0]) {
            $split[2] = $split[0];
        }
        push @csv, \@split;
    }
    return \@csv;
}

sub cluster {
    my $csv = shift;
    my @cluster = ();
    for my $array (@$csv) {
        push @{$cluster[$array->[2]]}, [$array->[1], $array->[0], $array->[3]]; # $cluster{<mod cl>} = [<seq>, <org cl>, <path>]
    }
    @cluster = sort { scalar(@$b) <=> scalar(@$a) } map { [ sort { $a->[0] <=> $b->[0] } @$_ ] } grep { $_ } @cluster;
    return \@cluster;
}

sub regen_html_header {
    return <<EOF;
<html><head><style>span.nobr{white-space:nowrap;}</style></head><body>
EOF
}

sub regen_html {
    my $uni = shift;
    my $html_dir = shift;
    my $cluster = shift;
    my $html = "<h1>$uni</h1>\n";
    my $i = 1;
    for my $c (@$cluster) {
        $html .= "<h2>Cluster $i: " . scalar(@$c) . " items</h2>\n";
        $html .= "<p>";
        for my $item (@$c) {
            my $img_loc = File::Spec->rel2abs($item->[2], $html_dir);
            my $size = html_imgsize($img_loc);
            $html .= qq(<span class="nobr"><img src="$item->[2]" $size>$item->[0]</span> );
        }
        $i++;
        $html .= "</p>\n";
    }
    return $html;
}

sub regen_csv_header {
    return join(',', 'mat', 'u', 'cluster', 'counts') . "\n";
}

sub regen_csv {
    my $uni = shift;
    my $mat = shift;
    my $cluster = shift;
    my $csv = '';
    my $i = 1;
    for my $c (@$cluster) {
        $csv .= join ',', $mat, $uni, $i, scalar @$c;
        $csv .= "\n";
        $i++;
    }
    return $csv;
}

sub gen_path {
    my $basedir = shift;
    return File::Spec->splitdir($basedir);
}

1;

日本古典籍字形データセットをかんたんに分類してくれるPythonスクリプト

#!/usr/bin/python3
# coding: utf-8

#
# Usage: Run on the directory just above where the Dataset of PMJT Character Shapes
# (http://codh.rois.ac.jp/char-shape/) is downloaded
#

from pathlib import Path
from time import time

from PIL import Image
from pyclustering.cluster.xmeans import xmeans
from pyclustering.cluster.center_initializer import kmeans_plusplus_initializer

ext = '.jpg'

kana = [
    'U+3042', 'U+3044', 'U+3046', 'U+3048', 'U+304A', 'U+304B',
    'U+304C', 'U+304D', 'U+304E', 'U+304F', 'U+3050', 'U+3051',
    'U+3052', 'U+3053', 'U+3054', 'U+3055', 'U+3056', 'U+3057',
    'U+3058', 'U+3059', 'U+305A', 'U+305B', 'U+305C', 'U+305D',
    'U+305E', 'U+305F', 'U+3060', 'U+3061', 'U+3062', 'U+3064',
    'U+3065', 'U+3066', 'U+3067', 'U+3068', 'U+3069', 'U+306A',
    'U+306B', 'U+306C', 'U+306D', 'U+306E', 'U+306F', 'U+3070',
    'U+3072', 'U+3073', 'U+3075', 'U+3076', 'U+3078', 'U+3079',
    'U+307B', 'U+307C', 'U+307E', 'U+307F', 'U+3080', 'U+3081',
    'U+3082', 'U+3084', 'U+3086', 'U+3088', 'U+3089', 'U+308A',
    'U+308B', 'U+308C', 'U+308D', 'U+308F', 'U+3090', 'U+3091',
    'U+3092', 'U+3093', 'U+309D', 'U+309E', 'U+30B5', 'U+30C4',
    'U+30CB', 'U+30F6'
        ]

basedir = Path.cwd()
now = time()

def detect_dirs(scandir=None)->list:
    '''
    detecting which directories would be scanned,
    which should be listed in the kana list
    '''
    if not scandir:
        scandir = Path(basedir)
    dirs = []
    for component in scandir.iterdir():
        if component.name in kana:
            dirs.append(component)
        elif component.is_dir():
            dirs += detect_dirs(component)
    return dirs

def scan_dir(scandir)->list:
    '''
    scanning the size of images
    it will dig a single-level of sub-directories
    '''
    if not scandir.is_dir():
        return []
    files = []
    scan_base = scandir.parts[-1]

    for f in sorted(scandir.iterdir()):
        if f.is_dir():
            for sf in sorted(f.iterdir()):
                if sf.name.endswith(ext) and sf.is_file():
                    files.append(is_img(sf))
        elif f.name.endswith(ext) and f.is_file():
            files.append(is_img(f))

    return files

def is_img(filename: str)->list:
    '''
    detect whether it is image file or not
    '''
    return [filename, img_size(filename)]

def img_size(filename: str)->list:
    '''
    return the size of images
    '''
    img = Image.open(filename, 'r')
    return list(img.size)

def calc_xmeans(files: list)->list:
    '''
    calculate the x-kernels
    '''
    sample = []
    for f in files:
        sample.append(f[1])

    if len(sample) < 15:
        amount = 1
    elif len(sample) > 50:
        amount = 3
    else:
        amount = 2

    xm_c = kmeans_plusplus_initializer(sample, amount).initialize()
    xm_i = xmeans(sample, xm_c, ccore=True)
#    xm_i = xmeans(sample, xm_c, ccore=False)    # Use this line on Darwin, and pray

    xm_i.process()
    clusters = xm_i.get_clusters()

    clgr = []

    j = 0
    for c in clusters:
        container = []
        for i in c:
            container.append([j, i, files[i]])
        clgr.append(container)
        j += 1

    return clgr

def export(data: list, directory)->None:
    '''
    export to html and csv calculated classifications
    '''
    export_csv = directory / (directory.parts[-1] + '.csv')
    export_html = Path(directory / '../../').resolve() / (directory.parts[-3] + '.html')
    if export_html.exists() and export_html.stat().st_mtime < now:
        with export_html.open(mode='w') as exh:
            exh.write('<html><head><style>span.nobr{white-space:nowrap;}</style></head><body>')

    with export_csv.open(mode='w') as exc:
        exc.write(','.join(['cluster no','seq','mod cluster no','file name']) + '\n')
        for c in data:
            for i in c:
                exc.write(','.join([str(i[0]), str(i[1]),'',\
                        str(item[2][0].relative_to(export_html.parent)) + '\n')

    with export_html.open(mode='a') as exh:
        exh.write('<h1>' + str(directory.parts[-1]) + '</h1>\n')
        exh.write('<p>The number of clusters: ' + str(len(data)) + '</p>\n')
        i = 0
        for c in data:
            i += 1
            exh.write('<h2>Cluster ' + str(i) + '</h2>\n')
            exh.write('<p>')
            for item in c:
                exh.write('<span class="nobr"><img src="' +\
                        str(item[2][0].relative_to(export_html.parent)) +\
                        '" width="' + str(item[2][1][0]) + '" height="' +\
                        str(item[2][1][1]) + '">' + str(item[1]) + '</span> ')
            exh.write('</p>\n')

dirs = detect_dirs()

for directory in sorted(dirs):
    dir_scan = scan_dir(directory)
    xm_clusters = calc_xmeans(files=dir_scan)
    export(data=xm_clusters, directory=directory)

pptx2md

#!/usr/bin/python
#coding: utf-8

import sys
from pptx import Presentation

if __name__ == '__main__':
  prs = Presentation(sys.argv[1])
  
  c = 1
  for slide in prs.slides:
    print("# Slide " + str(c))
    s = 1
    for shape in slide.shapes:
      if not shape.has_text_frame:
        continue
      if s == 1:
        print("## " + shape.text_frame.paragraphs[0].text + "\n")
      else:
        for paragraph in shape.text_frame.paragraphs:
         print("  " * (paragraph.level) + "*"),
         print(paragraph.text)
      s = s + 1
    if slide.has_notes_slide:
      print("\n"),
      print(slide.notes_slide.notes_text_frame.text + "\n")
    c = c + 1

必要があって。

Perlのregex?

正規表現でのメールアドレスチェックは見直すべき – ReDoS – yohgaki's blogを見て,Perl 5だとどうなるんだらうと書いてみた。ちなみに,perl 5.18.2とruby 2.0.0p648。

use strict;
use warnings;
use feature qw/ say /;
use Time::HiRes qw/ time /;

for my $n (5 .. 12) {
  my $s = "username\@host" . ".abcde" x $n . ".";
  my $start = time();
  say $s;
  say $s=~ /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/ms;
  say(length($s) . ': ' . (time - $start));
}

結果としては,

time perl test.pl

44: 0.000130891799926758

50: 0.0001068115234375

56: 0.000277042388916016

62: 0.000208139419555664

68: 0.000213861465454102

74: 0.000242948532104492

80: 0.00024104118347168

86: 0.000189065933227539

real	0m0.028s
user	0m0.017s
sys	0m0.008s
time ruby test.rb
nil
"44: 0.068262"
nil
"50: 0.385222"
nil
"56: 1.905355"
nil
"62: 9.483776"
nil
"68: 46.55787"
nil
"74: 230.308585"
nil
"80: 1156.772055"

real	24m5.527s
user	23m57.498s
sys	0m2.997s

といったところ。既知の結果ではありとくに云ふべきことないけれど,試してみるとほんとにぜんぜん違ふのは驚いた。

非欧文中の算用数字を漢数字に置換してくれるかもしれないマクロ

Macでも使へるやうにとRegExpなしでやってみたものの,よくわからないエラーで動かなかったので動くのかもよく知らない。もっとすっきり書けるのではと思ふが,VBA初心者すぎてよく分らない。

Option Explicit

'
' Arabic2HanInJa by Kazuhiro Okada
' A macro converts Arabic numerals in Japanese context into Han numerals
' Note that this macro converts entire file
' version 16 Oct 2017 Initial version
'

Sub Arabic2HanInJa()
' A main body
  Dim para As Paragraph
  Dim chr As Range
  Dim numFlag As Boolean
  Dim replFlag As Boolean
  Dim replBegin As Integer
  Dim replChr As Integer
  Dim chrNum As Integer

  For Each para In ActiveDocument.Paragraphs
    numFlag = False
    replFlag = True
    chrNum = 1
    For Each chr In para.Range
      With chr
        If .Text Like "[!a-zA-Z0-9 ]" Then
          If replFlag And numFlag Then
            For replChr = replBegin To chrNum
              para.Range.Characters(replChr).Text = _
                Arabic2Han(CInt(para.Range.Characters(replChr).Text))
            Next replChr
          Else
            replFlag = True
          End If
        Else
          If .Text Like "[0-9]" Then
            If numFlag = False Then
              numFlag = True
              replBegin = chrNum
            End If
          Else
            replFlag = False
          End If
        End If
      End With
      chrNum = chrNum + 1
    Next chr
    If numFlag And replFlag Then
      For replChr = replBegin To chrNum
        para.Range.Characters(replChr).Text = _
          Arabic2Han(CInt(para.Range.Characters(replChr).Text))
      Next replChr
    End If
  Next para
End Sub

Function Arabic2Han(num As Integer) As String
' A private function that converts Arabic numerals into Han
  Dim hanNumeral()
  hanNumeral = Array("〇", "一", "二", "三", "四", "五", "六", "七", "八", "九")
  Arabic2Han = hanNumeral(num)
End Function

外字を使ってWordデータを送ってきたときのこと

今後役に立つか分らないが,備忘までに。

状況

  • Office XMLなWordデータ
  • EUDC.TTE + EUDC.EUF
  • どっかの.TTFデータ

ここでOffice XMLぢゃないとたいへんかも? 外字の使用場所がかんたんに分るなら可。今回は,論文で,縦横に使ってゐた(やめてほしい……)。

外字データを画像データに変換する

今回のデータはわたしからさらに編集者に渡すものであったので,外字データがあるとよからぬことが起きないともかぎらない。そこで,外字データを画像にしてできるだけ埋め込みたい。

Googleで検索したところ,.TTEなデータは拡張子を.TTFにすればそのままPUAにグリフの割り当てられたTTFになることが分った。本機はMacなので,さうでないと困る(また,作業用のWinもないではないが,いろいろあって管理者権限は持ってゐない)。グリフを眺めたところ,デザインがひどかったが,時間もないので目を瞑る。またGoogleで,TTFから画像にできないか調べたところ,fontforgeのExport函数でできることが分った*1

どういふわけか,このままではまったく動かなかったのだけれど,このとほりにするといろいろ不都合があった。

  1. まづ,[Glyphs Worth Outputting]では,PUAにまんべんなくグリフがあるわけではないのに,PUA全体が選択されてしまふ。それは[Glyphs with only Splines]を選択することで解消された。
  2. つぎに,みつけたコードがうまく動かなかった。あれこれ試した結果,"Export("svg")"ならともかく出力することが分った*2
  3. しかし,それをそのまま画像化すると,文字の下が切れてしまふ。バウンディングボックスがなにか変らしい。さいしょはInkscapeをわざわざ入れてちくちく直してゐたが,200点ほどあるので自動化する必要があった。viewboxがどうもをかしいと分ったので*3,"perl -p -i -e 's/<svg viewBox="0 -145 1024 1024">/<svg viewBox="0 0 1024 1024">/;" *.svg"でどうにかする。
  4. ここまでくればmogrify*4。"mogrify -geometry 50x50 -format png *.svg"。ごちそうさまです。

なほ,どっかの.TTFデータも同様の手法で変換。こちらは*5この論文のためのものではなく,ぜんぶ扱ってしまふとまたよからぬことがあるので,どこで使用するか確認しなければならない。

Wordで使用した文字を確認する

.docxなwordデータは,zip圧縮されたファイルなので,解凍すれば中身が読める。あとはフォント名を頼りにリストアップ! がんばれ!!

できたデータをもとにperlで要らないファイルを消しました。ordが10進数で文字コードを返すのがゑぐかったです*6

Word XMLを書き換へる(失敗)

.docxなwordデータは,(中略)なので,画像を埋め込むのは難しくても,画像化したどのグリフを埋め込めばいいか分るやうにするくらゐの書き換へはできるんぢゃないかと思っていろいろやってみたが,どうにもうまくいかないのであきらめた。Mac OS Xだといらないファイルを勝手に付け足したりするので*7,それが原因なのかもしれないが,よく分らない。かといって,Wordではどの文字がどの文字コードかなど分らないので,XMLデータを眺めつつ,画像を埋め込むといふ非常にローテクなことをして作業完了。

*1:# Export all font elements to image with FontForge # # Usage # # 1. Open font wi - Pastebin.com

*2:いまにして思へば,"png"とかでもよかったのだらうか?

*3:"<svg viewBox="0 -145 1024 1024">"ってどういふことなのか。

*4:mognifyって覚えてた。謎w

*5:ひろく見れば外字データも使はない文字があるのだが

*6:ゑぐくはない

*7:Windowsがお行儀がばっちりなわけではないが,相対的に。