,区切りのデータ列の整形についてNo.10266
tack さん 23/12/14 23:23
 
いつもお世話になっております。
毎回のようにお伺いさせていただくのも恐縮ではありますが、上手くいかないのでご
助言をいただきたく思います。

例文にあるような内容を,区切りで各々の順番に対応させて組み変えるのが目的です。
例文の要素は必ず,区切りになっています。

【例文】
"articles":["【開始】","「そんなわけで、今年もパーティーを開催する予定だから
","そのつもりでよろしく」","今年もこの時期がやってきた。","理由もわからない、
謎のパーティー。","いったい何を祝ってるのかはいまだに不明だけど、","例年参加
している。","【暗転】","「パーティー用のドレスやスーツは、今年も貸し出しする
んですか?」","「ええ、数に限りがあるから早いもの順だけど」"],
"characters":[[show-bg][play-voice]["0"],["1"],["2"],["3"],["4"],["5"],["6"],
[play-voice]["7"],["8"],[play-voice]["9"]],

【求める結果】
"【開始】",[show-bg][play-voice]["0"]
"「そんなわけで、今年もパーティーを開催する予定だから",["1"]
"そのつもりでよろしく」",["2"]
"今年もこの時期がやってきた。",["3"]
"理由もわからない、謎のパーティー。",["4"]
"いったい何を祝ってるのかはいまだに不明だけど、",["5"]
"例年参加している。",["6"]
"【暗転】",[play-voice]["7"]
"「パーティー用のドレスやスーツは、今年も貸し出しするんですか?」",["8"]
"「ええ、数に限りがあるから早いもの順だけど」"[play-voice]["9"]

【現在の結果】
●DATA1
"articles":[
"【開始】",
「そんなわけで、今年もパーティーを開催する予定だから",
そのつもりでよろしく」",
今年もこの時期がやってきた。",
理由もわからない、謎のパーティー。",
いったい何を祝ってるのかはいまだに不明だけど、",
例年参加している。",
【暗転】",
「パーティー用のドレスやスーツは、今年も貸し出しするんですか?」",
「ええ、数に限りがあるから早いもの順だけど」",
"[show-bg][play-voice]["0"]"
"["1"]"
"["2"]"
"["3"]"
"["4"]"
"["5"]"
"["6"]"
"[play-voice]["7"]"
"["8"]"
"[play-voice]["9"]"


【マクロ】
setcompatiblemode 0x00020000;
loaddll "hmjre.dll"; // HmJre.dll のロード ※2
disabledraw;
begingroupundo;//一撃で元に戻すためにグループ化

//並べ替えマクロ
//--------------------------------------
gofiletop;
#i = 0;
while( 1 )
{
searchdown2 "\"articles\":\\[.+?\\]", regular;
if( !result ) break; // while() ループの終了
$art[#i] = gettext( foundtopx, foundtopy, foundendx, foundendy );
//message "Article Data: " + $art[#i];

searchdown2 "(?\\2)(\"characters\":\\[)(.+?\\])(\\],)", regular;
if( !result ) { message "\"characters\":が見つかりません"; goto END; }
$cha[#i] = gettext( foundtopx, foundtopy, foundendx, foundendy );
//message "Character Data: " + $cha[#i];
#i = #i + 1;
}


#data_cnt = #i; // テキストデータの総数を格納

gofileend;
#i = 0;
while( #i < #data_cnt )
{
//「,」を区切り文字としてデータを分割し配列に格納 ※3
#c1 = split( $c, $cha[#i], "," );

// 見出しを追加
$s = "\n●DATA" + str( #i + 1 ) + "\n";
//整形
$s = $s + "\"" + $art[#i] + "\",\n";
$s = dllfuncstr( "ReplaceRegular", "\\[", $s, 0, "\\[\n", 2 );
$s = dllfuncstr( "ReplaceRegular", ",\"", $s, 0, "\",\n", 2 );


#j = 0;
while( #j < #c1 )
{

// データの並び替え
$s = $s + "\"" + $c[#j] + "\"\n";
#j = #j + 1;
}

//「""」を「"」に置換
$s = dllfuncstr( "ReplaceRegular", "\"\"", $s, 0, "\"", 2 );
// データの書き込み
insert $s;

#i = #i + 1;
}

END:

//--------------------------------------

endgroupundo;
endmacro;

[ ]
RE:10266 ,区切りのデータ列の整形についNo.10267
こみやんま さん 23/12/15 01:10
 
重要なのは テキスト面上や文字列内で正規表現ぐりぐりで勝負しようとするのでは
なく、
「いかに取り扱いやすいオブジェクト形式」にするか、です。

「取り扱いにくいデータ」→「プログラム的に取り扱える(操作しやすい)オブジェク
ト」→「そのオブジェクトを使って改めて別の文字列を構築」
という考え方を「徹底する」ように癖をつけておかないと、
こういったことを疎かにすると、10年やってもほとんど成長しないですよ。

こういう癖をつけておくようにすると「内容によって出来たり出来なかったり」とい
ったことはなくなり
「理論的に可能な加工は全て短時間で自信で実装出来る」という安定した結果をご自
身で得られるようになります。

他のエディタに移っても100%通用する、同じ考え方で済むようにもなります。


jsmode "WebView2";
js {



// テキストからarticlesを配列的に取得
function getArticles(text) {
    let matchesA = text.match(/"articles":\[(.+)\],$/m);
    if (matchesA) {
        let articles = matchesA[1].split(",");
        return articles;
    }
}

// テキストからcharactersを配列的に取得
function getCharacters(text) {
    let matchesA = text.match(/"characters":\[(.+)\],$/m);
    if (matchesA) {
        let characters = matchesA[1].split(",");
        return characters;
    }
}

function getFormatText(articles, characters, format) {
    if (articles.length == characters.length) {
        let length = articles.length;
        let output_text = "";
        for(let i=0; i<length; i++) {
            output_text += sprintf(format, articles[i], characters[i]);
        }
        return output_text;
    }
    throw "articlesとcharactersで要素の個数が異なります。";
}

// ここでまともにプログラム的に取り扱える配列にできるようにしておく(刹那的に
やるのではなく、このような考え方がとても重要)

function main() {

    try {
        let text = hidemaru.getSelectedText();
        if (!text) {
            throw "対象のテキストが空です。";
        }
        let articles = getArticles(text);
        if (!articles) {
            throw "articlesが正しく把握できません。";
        }

        let characters = getCharacters(text);
        if (!characters) {
            throw "charactersが正しく把握できません。";
        }

        let result_text = getFormatText(articles, characters, '%s,%s\r\n');

        begingroupundo();
        selectall();
        insert(result_text);
        endgroupundo();
    } catch(ex) {
        let o = loaddll( "HmOutputPane.dll");
        o.dllfunc.Output(hidemaru.getCurrentWindowHandle(),ex);
    }
}

main();



} // js



[ ]
RE:10267 ,区切りのデータ列の整形についNo.10268
tack さん 23/12/15 01:23
 
なるほど、まずはオブジェクトを共通の形に作り替えてそれを扱うイメージを持つ、
ですね。

今回の場合
    try {
        let text = hidemaru.getSelectedText();
        if (!text) {
            throw "対象のテキストが空です。";
        }
「対象のテキストが空です。」を返すのです。
返す結果の内容は正しい結果なのですが、今回のマクロではここで動作停止をしてい
まいますが必要な要素なのでしょうか?

[ ]
RE:10268 ,区切りのデータ列の整形についNo.10269
こみやんま さん 23/12/15 01:40
 
テキストを選択してなかったら、対象のテキストが空になる、ということです。

そこは選択にするのか、なんか変数に格納しておいて、それを対象にするのか、決め
ればよいかと。
(矩形選択は hidemaru.getSelectedText() の対象にはならないので、矩形選択を対
象にするのであれば、そのようにくめばよいでしょう。)

JSでのやりかたがよくわからない、という場合は、
例えば、「秀丸マクロの層」で

$TEXT = ***** (対象の文字列を格納);

jsの層で

let text = getVar("$TEXT");

(秀丸マクロの$TEXTの変数の内容をjs側に拾う)
などといったやりかたもあります。





あとは、もしも articles の文字列自体(セリフのなか)にも「,」が入ることがあ
るということであれば、以下のように少し工夫が必要でしょう。
(単純な , で分離というわけにはいかなくなるので、正しくなるであろうパーサーを
通す)

// テキストからarticlesを配列的に取得
function getArticles(text) {
    let matchesA = text.match(/"articles":(\[.+\]),$/m);
    if (matchesA) {
        // let articles = matchesA[1].split(",");
        let articles = JSON.parse(matchesA[1]);
        articles = articles.map(s=>JSON.stringify(s));
        return articles;
    }
}

[ ]
RE:10269 ,区切りのデータ列の整形についNo.10270
tack さん 23/12/15 02:31
 
理解が悪く申し訳ありません、お聞きさせていただきたいのですが
今回のこのマクロでは配列的に取得した文字列はアウトプット枠で見る、琴も含めプ
ログラム的に別の文字列を構築する部分を目標にしているのでしょうか?
(アウトプット枠を使ったことが無いので、よく理解できていないのもありますが)

テキストの選択とあり、$TEXT = ***** (対象の文字列を格納);
とされていますが今回の動作で言えば、配列的に取得した内容をこの部分でアウトプ
ットさせるために必要、となるのでしょうか?

articlesの文字列内に,は存在しないのでそちらは問題ありません。

[ ]
RE:10266 ,区切りのデータ列の整形についNo.10271
igus さん 23/12/15 13:10
 
//データ列整形.mac
begingroupundo;
gofiletop;beginsel;searchdown "[";right;delete;
golineend2;beginsel;searchup "]";delete;
golinetop2;beginsel;golineend2;
$a0=getselectedtext();selectline 1;delete;
gofiletop;beginsel;searchdown "[";right;delete;
golineend2;beginsel;searchup "]";delete;
golinetop2;beginsel;golineend2;
$b0=getselectedtext();selectline 1;delete;

#n1=split($a,$a0,",");
#n2=split($b,$b0,",");
#i=0;
while(#i<#n1){
  insert $a[#i] +","+ $b[#i]+"\n";
  #i=#i+1;
}
endgroupundo;
endmacro;

[ ]
RE:10270 ,区切りのデータ列の整形についNo.10272
こみやんま さん 23/12/15 13:46
 
今回アウトプット枠は、「エラー」を表示するのに利用しています。


>理解が悪く申し訳ありません、お聞きさせていただきたいのですが
>今回のこのマクロでは配列的に取得した文字列はアウトプット枠で見る、琴も含め
>プログラム的に別の文字列を構築する部分を目標にしているのでしょうか?


なんらかのテキストを対象に、articles と characters を(ちゃんと要素ごとに)し
っかりとした形でオブジェクトに格納し、

それらの道具を使って、最終的な出力をだせる、

そのように組んでいます。

最悪な考え方は「エディタ面」に対して、やれ「〇〇」の文字を発見しては
カーソルを右へ下へと動かしてなんとかしようとすることで、
ちょっとフォーマットが変わっただけで
大破綻しますし、そのような考え方は秀丸エディタでしか通用しませんし、
そもそも何をやろうとしているのか作業内容とマクロ内容の照らし合わせも一致しま
せん。

//--------------------------------------------------------------

今回最終的な結果を考える時、どのように頭で考えておられますか?
何かを発見してはカーソルを右へ動かしたり、下へ動かしたりと
考えますか? そのような考え方は決してしないはずです。


概ね以下のように考えているハズです

@テキスト全体をなんらかの形で得る
 ・編集エリアにあるテキストなのか、
 ・それとも全体ではなく、あくまで選択したテキストなのか、
 ・あるいは秀丸で開いてもいないファイルなのか

Aarticlesの要素(何個あるかは不明)だが配列的になっているので配列的に捉える

Bcharactersの要素(何個あるかは不明)だが配列的になっているので配列的に捉える

Carticlesとcharactersについて、各々前から1つずつ取り出し、articles[i番目],
 characters[i番目]のテキスト内容を「行」とする。

D Cを要素数の終了まで繰り返す。

E最終的に得た全体としての文字列内容をどこかに出す
 ・現在開いている編集エリアに上書きするのか
 ・別の編集エリアにするのがよいのか?
 ・選択しているテキストの「選択範囲だけを」置き換えるのがよいのか?

//--------------------------------------------------------------

このように多くの人が考えるハズです。
だからこそ、「そのように組んでおけば」誰が見てもわかりやすく、
何か仕様が変化しても、「どこを修正すれば」、最初から「ここからここの範囲だけ
修正すればよい」と、わかりやすいのです。

ここを踏み外すと、フォーマットが少し変わっただけで「いちからアルゴ考え直さな
きゃ・・・」とかなってしまいます。



    try {
        let text = hidemaru.getSelectedText(); // @に相当。今回は編集エリア
において、選択したテキストを対象とする
        if (!text) {
            throw "対象のテキストが空です。";
        }
        let articles = getArticles(text); // Aに相当
        if (!articles) {
            throw "articlesが正しく把握できません。";
        }

        let characters = getCharacters(text);
        if (!characters) {
            throw "charactersが正しく把握できません。"; Bに相当
        }

        //  CDに相当し、返り値 result_textが「加工した後の全体の文字列」
(本当はCとDは分けた方が良いですが今回ははしょっている)
        let result_text = getFormatText(articles, characters, '%s,%s\r\n');
       

        // 今回は「加工した後の全体の文字列」をたまたま編集ペインに上書きし
ている。
        begingroupundo();
        selectall();
        insert(result_text);
        endgroupundo();

    // 何かエラーがあった時に、「どのような項目でエラーがあったのか・何が原
因なのか」がわかるようにしている。
    // 対象のテキストが得られないかも?
    // articlesのところのフォーマットが変化して捉えられなくなったのかも?
    // charactersのところのフォーマットが変化して捉えられなくなったのかも?
    // articlesとcharactersは要素数が一致しなければならないはずなのに、不思
議と要素数が一致していないのかも?
    } catch(ex) {
        let o = loaddll( "HmOutputPane.dll");
        o.dllfunc.Output(hidemaru.getCurrentWindowHandle(),ex);
    }





[ ]
RE:10271 ,区切りのデータ列の整形についNo.10273
igus さん 23/12/15 14:17
 
//データ列整形.mac
//無駄な動きが多すぎたのでやり直してみる
begingroupundo;
gofiletop;searchdown "[";right;beginsel;golineend2;searchup "]";
$a0=getselectedtext();selectline 1;delete;
gofiletop;searchdown "[";right;beginsel;golineend2;searchup "]";
$b0=getselectedtext();selectline 1;delete;

#n1=split($a,$a0,",");
#n2=split($b,$b0,",");
#i=0;
while(#i<#n1){
  insert $a[#i] +","+ $b[#i]+"\n";
  #i=#i+1;
}
endgroupundo;endmacro;



[ ]
RE:10273 ,区切りのデータ列の整形についNo.10274
tack さん 23/12/16 14:46
 
>>こみやんま様
アウトプット枠がエラーがある場合の確認用なのですね。確かにエラーが起きたり、
結果が上手く返らない場合にmessage文を使って確認することが多いので使い慣れれ
ば便利そうです。
仰る通り、@からEの通りにできればと考えており、フォーマットを整えてからを行
えることが最良と思っております。
またご指摘いただいた通り、
@の項目は、秀丸で開いたファイルであり、全体を整えつつ、該当箇所のデータ区切
りがある部分を整形したいと考えています。
Eについてはデータ該当箇所に上書きもしくは、同テキストファイルの一番下に書き
出し、データ整形のための該当箇所は削除を順に行う、のどちらかを考えていました。

その上で、でありますが
モデル文章にマクロを行ってみても結果が返ってこなかったのです、それは、記載い
ただいたマクロのままでは

    try {
        let text = hidemaru.getSelectedText();
        if (!text) {
            throw "対象のテキストが空です。";
        }
>>テキストを選択してなかったら、対象のテキストが空になる、ということです。
>>そこは選択にするのか、なんか変数に格納しておいて、それを対象にするのか、決
>めればよいかと。

とお答えいただいた内容に対し、選択を指定していないから結果として返せる状態に
ならない、という理解でよろしいでしょうか?

今回のマクロは、フォーマット形成とデータ整形を一緒に行えればと思い、先にフ
ォーマット形成(replace構文)を行い、その後整形分を実行しようと思っています。
データ整形のテキストエリアは例文にある通り "articles" 〜 "characters" の
最後までを想定しています。
("characters"の最後=最終行になるよう形成しています)

この場合、$TEXT = ***** (対象の文字列を格納);をどこに加えればよいのでしょうか?
また理解すべき箇所がずれていますでしょうか?

>>igus様
いつも教えていただきありがとうございます。
今回のこみやんま様に対する回答の中で申し上げてしまいましたが、例文はテキスト
の一部になっています。お伝えが後になってしまい申し訳ありません。

例文に対し、マクロを行ったところ期待した結果を返されたので満足しているのです
が、フォーマット形成と一緒に走らせるとフォーマット形成を行い、マクロが終了し
ます。(エラーは返さず)
再度同様にマクロを実行すると、データ整形(区切り)が上手く動作しました。
この際フォーマット形成部が、該当部分に対し2度目のマクロで影響を及ぼしていな
いことは確認しております。
2度マクロを実行しないと整形が上手くいかないのは、テキストファイルのカーソル
位置が悪いのかと思い golinetop;なども加えて見ても変わらず、となりました。
原因が思いつかないのですが、何か別に要因が噛み合うことがあったりするのでしょ
うか?

[ ]
RE:10274 ,区切りのデータ列の整形についNo.10275
igus さん 23/12/16 16:22
 
//データ列整形.mac
setcompatiblemode 0x00020000;
loaddll "hmjre.dll"; // HmJre.dll のロード ※2
disabledraw;begingroupundo;

gofiletop;
#i=0;
while(1){
  searchdown2 "\"articles\":\\[.+?\\]", regular;
  if( !result ) break;
  $art[#i]=gettext(foundtopx,foundtopy,foundendx,foundendy);
  //message "Article Data: " + $art[#i];
    searchdown2 "(?\\2)(\"characters\":\\[)(.+?\\])(\\],)", regular;
  if( !result ) { message "\"characters\":が見つかりません"; goto END; }
    $cha[#i] = gettext( foundtopx, foundtopy, foundendx, foundendy );
  //message "Character Data: " + $cha[#i];
  #i=#i+1;
}

#data_cnt=#i; // テキストデータの総数を格納
gofileend;
#i=0;
while(#i<#data_cnt){
  //「,」を区切り文字としてデータを分割し配列に格納 ※3
  #c1=split($c,$cha[#i],",");
 
  // 見出しを追加
  $s2="\n●DATA"+str(#i+1)+"\n";
  //整形
  $s=$s+"\""+$art[#i]+"\",\n";
  $s=dllfuncstr("ReplaceRegular","\\[",$s,0,"\\[\n",2);
  $s=dllfuncstr("ReplaceRegular",",\"",$s,0,"\",\n",2);
 
  //message $s;
  #n=split($s1,$s,"\n");
  #j=0;
  while(#j<#c1){
    //データの並び替え
    $s2=$s2+$s1[#j+1]+"\""+$c[#j]+"\"\n";
    #j=#j+1;
  }
  //message $s2;
  //「""」を「"」に置換"
  $2s=dllfuncstr("ReplaceRegular","\"\"",$s2,0,"\"",2);
  //データの書き込み
  insert $s2;
  #i=#i+1;
}

END:
endgroupundo;endmacro;

[ ]
RE:10275 ,区切りのデータ列の整形についNo.10276
igus さん 23/12/16 16:28
 
tackさんのをベースにそれっぽくしてみましたが $s2 が $2s になってたりかなり適
当ですね、細部は調整してみてください

[ ]
RE:10274 ,区切りのデータ列の整形についNo.10278
こみやんま さん 23/12/17 17:00
 

jsmode "WebView2";
js {



// テキストからarticlesを配列的に取得
function getArticles(text) {
    let matchesA = text.match(/"articles":(\[.+\]),$/m);
    if (matchesA) {
        // メッセージの途中で途中で「,」などがあっても正しくなるようにしてお
く。「日本語」の時点では決して無いと思い込んでいても、
        // おもいもよらず英語化されて普通にメッセージ中に","が登場するように
なってしまいました、などというのは当たり前のようにあることである。
        let articles = JSON.parse(matchesA[1]);
        articles = articles.map(s=>JSON.stringify(s));
        return articles;
    }
}

// テキストからcharactersを配列的に取得
function getCharacters(text) {
    let matchesA = text.match(/"characters":\[(.+)\],$/m);
    if (matchesA) {
        let characters = matchesA[1].split(",");
        return characters;
    }
}

// artcilesの配列とcharactersの配列と、sprintf用のformatを使って成形
function getFormatText(articles, characters, format) {
    if (articles.length == characters.length) {
        let length = articles.length;
        let output_text = "";
        for(let i=0; i<length; i++) {
            output_text += format(articles[i], characters[i]);
        }
        return output_text;
    }
    throw "articlesとcharactersで要素の個数が異なります。";
}


// 対象のテキストを分析して置き換えたテキストを返すことを試みる。失敗したら
元のテキストをそのまま返す
function tryCustomFormat(target_text) {

    try {
        let text = target_text;
        if (!text) {
            throw "対象のテキストが空です。";
        }
        let articles = getArticles(text);
        if (!articles) {
            throw "articlesが正しく把握できません。";
        }

        let characters = getCharacters(text);
        if (!characters) {
            throw "charactersが正しく把握できません。";
        }

        let format = (art, chr) => { return art + "," + chr + "\r\n"; }
        let result_text = getFormatText(articles, characters, format);

        // 成功してたら成形後のテキストを返しておく
        return result_text;
    } catch(ex) {
        let o = loaddll( "HmOutputPane.dll");
        o.dllfunc.Output(hidemaru.getCurrentWindowHandle(), ex + "\r\n");
    }

    // 失敗してたら元のテキストをそのまま返しておく
    return target_text;
}

function main() {
    let all_text = hidemaru.getTotalText(); // 開いているファイルの全文字列

    // "articles〜characters"の2行の文字列を、1つの塊(chunk)データとみなし、
    // これを(発見するたびに) tryCustomFormat で変換を試みる
    // これを発見する度に行う。all_textに対する全てのテキストの変換結果が全
体の文字列としてresult_textに入る。
    let result_text = all_text.replace(
        /(^"(articles":.+?\n"characters":.+?(\n|$)))/msg,
        (match, chunk) => { return tryCustomFormat(chunk); } // tryCustomFor
matで変換を試みる
    );
   
    begingroupundo();
    selectall();
    insert(result_text);
    endgroupundo();
}


main();

} // js



[ ]
RE:10278 ,区切りのデータ列の整形についNo.10279
こみやんま さん 23/12/17 18:01
 
元データがたとえ「10万行程度」あったとしても
このJavaScript系のプログラムならば変換に要する時間は1〜2秒程度(使ってるCPUに
依存しますが..)だと思いますが、
20万行程度(実際にはファイルサイズが10M越え)を超えると、このプログラム自体の
変換は一切問題がないのですが、
Ctrl+Zで元に戻そうとすると、秀丸本体側の都合で「バッファが足りない」と言われ
ますので、
元データが大きいようならば(1パック化したような)、
記憶バッファーを大きくした方がよいでしょう(多分秀丸の設定のどこかにあります)

[ ]
RE:10278 ,区切りのデータ列の整形についNo.10288
tack さん 24/01/23 22:37
 
>>igus様
返信が大変遅くなり申し訳ありません
いつもアドバイスをいただき、本当にありがとうございます
少し読み解いてみて考えてみたいと思います!

>>こみやんま様
返信が大変遅くなり申し訳ありません
確かに日本語の時点でない、と思い込むのは危ういと思いました
バッファは大きめに設定してますが、それでもと思いますので状況に応じて設定を変
更しようと思います
こみやんま様の内容ももう少し時間を掛けて読み解きたいと思います、アドバイスあ
りがとうございます!

[ ]