ラクラク罫線

提供: MeryWiki
2025年11月24日 (月) 19:15時点におけるMSY-07 (トーク | 投稿記録)による版 (コメントの追加)
ナビゲーションに移動 検索に移動

概要

  • 罫線での表組み作成を支援するマクロです。
  • マウスの指定だけで、文字列を囲んだり、タテ・ヨコの罫線を引くことができます。

使い方

  • 行(複数行の場合は、一番上の行)の最左にカーソルを置いてマクロを実行すると、その行を罫線で囲みます。
  • 最下行の最左にカーソルを置いてマクロを実行すると、罫線の調整を行います。
  • 単一行をダブルクリック等で選択した場合は、その行に対してのみ罫線の調整を行います。
  • マウスドラッグで同一行内の範囲を選択してマクロを実行すると、選択した文字の下にヨコ罫線を引きます。このとき、選択領域の両端が罫線かつその間が全角空白のみであれば、全角空白をヨコ罫線に置きかえます。
  • 行の最後から選択を始めると、最後の文字が罫線のときはその罫線の前に全角空白を挿入し、そうでないときは全角空白を最後尾に追加します。
  • 上記以外の選択をしてマクロを実行すると、最上部の選択文字の左側の位置で選択行に渡ってタテ罫線を引きます。

(例1)
abcd
abcd
  ↓ 1行目のaの左にカーソルをおいて実行。
┌────┐
│abcd│
│abcd│
└────┘

(例2)
abcd
0123
ABCD
  ↓ 1行目のaとbの間から2行目まで(途中でも可)を選択して実行。(タテ罫線)
  ↓ 1行目のbcdを選択して実行。(ヨコ罫線)
  ↓ 3行目の0から3までを選択して実行。(ヨコ罫線)
  ↓ 1行目のaの左にカーソルをおいて実行。(囲み)
  ↓ 7行目の└の左にカーソルをおいて実行。(調整)
┌─┬───┐
│a│bcd│
│ ├───┤
│0│123│
├─┴───┤
│ABCD │
└─────┘

(例3)
abcd
  ↓ aとbの間にカーソルを置いて実行。(タテ罫線)
  ↓ cとdの間にカーソルを置いて実行。(タテ罫線)
  ↓ bcを選択して実行。(ヨコ罫線)
  ↓ もう一度bcを選択して実行。(ヨコ罫線)
  ↓ abcdを罫線の行の間にD&Dする。
  ↓ 最下行の行頭の空白の左にカーソルをおいて実行。(調整)
 ┌──┐
a│bc│d
 └──┘

備考

  • 表の前後に空行があると仮定しています。ない場合は、選択行の前後35行を表とみなします。
  • 調整(罫線置換)の対象は、罫線と全角空白です。
  • 半角文字にはある程度まで対応しています。が、微妙にずれます。マクロでは極力削除しないようにしているので微調整は手作業でお願いします。

変更履歴

  • 1.0.3 (2012-12-22)
    • 囲みと調整の指示方法を行末から行頭に変更しました
    • 行末から選択を開始しているときは、最後の罫線を右に動かすようにしました
    • 内部構成を変更しました
  • 1.0.2 (2012-12-21)
    • 一定の場合、単一行に対して罫線調整を行うようにしました
  • 1.0.1 (2012-12-17)
    • 一定の場合、連続する全角空白をヨコ罫線に置き換えるようにしました
  • 1.0.0 (2012-12-15)
    • 初版

ソースコード

// ruledliner.js(ラクラク罫線.js)
// 最終更新:2012/12/22

var notch = "";
var depth = 35;
var single_spc = " "
var bouble_spc = " ";

// -- prototype関数 --
// String
// 桁揃えに使う文字。
String.prototype.spcS = single_spc;
String.prototype.spcW = bouble_spc;
// インデックス番号iの文字が半角であるかどうか。
String.prototype.isHan = function( i ){
	// Shift_JIS: 0x0 ~ 0x80, 0xa0 , 0xa1 ~ 0xdf , 0xfd ~ 0xff
	// Unicode : 0x0 ~ 0x80, 0xf8f0, 0xff61 ~ 0xff9f, 0xf8f1 ~ 0xf8f3
	var c = this.charCodeAt( i );
	return ( ( c >= 0x0 && c < 0x81 ) || ( c == 0xf8f0 ) || ( c >= 0xff61 && c < 0xffa0 ) || ( c >= 0xf8f1 && c < 0xf8f4 ) );
}
// 文字列の長さ。半角換算。
String.prototype.blength = function(){
	var blen = 0;
	for ( var i = 0; i < this.length; i++ ){
		( this.isHan( i ) )? blen += 1: blen += 2;
	}
	return blen;
}
// インデックス番号の変換(半角→通常)。
String.prototype.bindex2i = function( bi ){
	if ( bi < 0 ){Alert( "bi < 0 in bindex2i !!" );Quit()}
	var n = 0;
	for ( var i = 0; i < this.length; i++ ){
		if ( !this.isHan( i ) ){ n++ };
		if ( i + n >= bi ){ return i }
	}
	return bi
}
// 文字列から指定した位置の文字を抜き出し。半角指定。
String.prototype.bcharAt = function( bi ){
	var i = this.bindex2i( bi );
	if ( bi >= this.blength() ){ i = this.length }
	return this.charAt( i );
}

// 文字列を分割。半角指定。
String.prototype.bdiv2ary = function( bi ){
	var i = this.bindex2i( bi );
	return { front: this.substr( 0, i ), tail: this.substr( i, this.length - i ) }
}
// 文字列の繰り返し。
String.prototype.repeat = function( n ){
	var str = this
	var s = "";
	while ( n > 0 ){
		if( n & 1 ){ s += str }
		n = n>>1;
		str += str;
	}
	return s;
}
// 文字列を指定桁数まで延長。半角指定。
String.prototype.beven = function( bn ){
	switch ( arguments.length ){
		case 0: bn = 0;
	}
	var re = new RegExp( this.spcS + "+$", "ig" );
	var str = this;
	str = str.replace( re, "" );
	var s = "";
	if ( bn == 0 ){
		s =  str + this.spcS.repeat( str.blength() % 2 )
	}else{
		bn += bn % 2;
		s =  str + this.spcS.repeat( bn - str.blength() );
	}
	var re = new RegExp( this.spcS + "{2}", "ig" );
	s = s.replace( re, this.spcW );
	return s;
}

// 最長要素の長さ。半角換算。
Array.prototype.maxbLen = function(){
	var blen = 0;
	for ( var i = 0; i < this.length; i++ ){
		if ( typeof( this[i] ) != "string" ){ Alert( "maxbLen!" ); Quit() }
		var s = this[i].blength();
		( blen > s )? blen: blen = s;
	}
	return blen;
}


// -- 罫線を引くオブジェクト --
TblRuler = function(){
    this.initialize.apply( this, arguments );
}
TblRuler.prototype = {
	spcS : single_spc,
	spcW : bouble_spc,
	action : "none",
	tbl : [],
	x1 : 0,
	y1 : 0,
	x2 : 0,
	y2 : 0,
	initialize : function( tblTop, tblBottom, tx, ty, bx, by, str ){
		this.tbl = str.split( "\n" );
		this.y1 = ty - tblTop;
		this.x1 = this.tbl[ this.y1 ].substr( 0, tx-1 ).blength();
		this.y2 = by - tblTop;
		this.x2 = this.tbl[ this.y2 ].substr( 0, bx-1 ).blength();
		this.tx = tx;
		this.bx = bx;
	},
	length : function(){ return this.tbl.length},
	vline : function(){
		this.action = "vline";
		var vksen = "│";

		// 選択行の事前処理。
		var l_ary = [];
		var r_ary = [];
		for ( var i = this.y1; i <= this.y2; i++ ){
			var ft = this.tbl[i].bdiv2ary( this.x1 );
			l_ary.push( ft.front );
			r_ary.push( ft.tail );
		}
		for ( var i = 0; i < r_ary.length; i++ ){
			var r_aryi = r_ary[i].split( vksen );
			r_aryi[0] = r_aryi[0].beven();
			r_ary [i] = r_aryi.join( vksen );
		}
		var lenL = l_ary.maxbLen();
		var lenR = r_ary.maxbLen()

		for ( var i = 0; i < this.tbl.length; i++ ){
			if ( i >= this.y1 && i <= this.y2 ){
				this.tbl[i] = l_ary[ i - this.y1 ].beven( lenL ) + vksen + r_ary[ i - this.y1 ];
			}
		}
	},
	vline_at_end: function(){
		this.action = "vline_at_end";

		for ( var i = this.y1; i <= this.y2; i++ ){
			var work = this.tbl[ i ].split( "" );
			var rend = work.pop();
			if ( /[─│┌┐└┘├┤┬┴┼]/.test( rend ) ){ work.push( this.spcW + rend ) }else{ work.push( rend + this.spcW ) };
			this.tbl[ i ] = work.join ( "" );
		}
	},
	hline : function(){
		this.action = "hline";
		var hksen = "─";
		
		var s = this.tbl[ this.y1 ].slice( tx - 1, bx - 1 );
		var regK = new RegExp( "[─│┌┐└┘├┤┬┴┼]", "i" ); 
		var reg = new RegExp( "[^" + this.spcW + "]+","i" ); 
		var mid = s.slice( 1, s.length - 1 );
		if ( s.length >= 3 && regK.test(s.charAt(0)) && regK.test(s.charAt( s.length - 1 )) && !reg.test( mid )){
			var reg2 = new RegExp( this.spcW ,"ig" ); 
			var c = this.tbl[ this.y1 ].split("")
			s = s.replace( reg2, hksen );
			c.splice( tx - 1, bx - tx, s );
			this.tbl[ this.y1 ] = c.join("");
		}else{
			var xmax = 0;
			if ( this.y1 >= this.tbl.length - 1 ){
				xmax = this.tbl[ this.y1 ].blength();
			}else{
				xmax = Math.max( this.tbl[ this.y1 ].blength(), this.tbl[ this.y1 + 1 ].blength() );
			}
			var s1 = this.spcW.repeat( ( this.x1 + 1 ) / 2 );
			var s2 = hksen.repeat( ( this.x2 - this.x1 + 1) / 2 );
			var s3 = this.spcW.repeat( ( xmax - s1.blength() - s2.blength() + 1 ) / 2 );
			var s = s1 + s2 + s3;
			this.tbl.splice( this.y1 + 1, 0, s );
		}
	},
	makeFrame : function(){
		this.action = "makeFrame";
		var re = new RegExp( "[" + this.spcS + this.spcW + "]+$", "i" );
		for ( var i = 0; i < this.tbl.length; i++ ){
			this.tbl[i] = this.tbl[i].replace( re, "" );
		}
		var len = this.tbl.maxbLen() + this.tbl.maxbLen() % 2;
		for ( var i = 0; i < this.tbl.length; i++ ){
			this.tbl[i] = "│" + this.tbl[i].beven( len ) + "│";
		}
		this.tbl.unshift( "┌" + "─".repeat( len / 2 ) + "┐" );
		this.tbl.push( "└" + "─".repeat( len / 2 ) + "┘" );
	},
	linkChk : function( bx, y ){
		var s = 0;
		// 上は?
		if ( y >= 1 ){
			var c = this.tbl[ y-1 ].bcharAt( bx );
			if ( /[┌┐│┬├┼┤]/.test( c ) ){ s += 8 }
		}
		// 下は?
		if ( y < this.tbl.length - 1 ){
			var c = this.tbl[ y+1 ].bcharAt( bx );
			if ( /[└┘│┴├┼┤]/.test( c ) ){ s += 4 }
		}
		// 左は?
		if ( this.tbl[ y ].bindex2i( bx ) >= 1 ){
			var c = this.tbl[y].charAt( this.tbl[y].bindex2i( bx ) - 1 );
			if ( /[┌└─┬┼├┴]/.test( c ) ){ s += 2 }
		}
		// 右は?
		if ( this.tbl[ y ].bindex2i( bx ) < this.tbl[y].length - 1 ){
			var c = this.tbl[y].charAt( this.tbl[y].bindex2i( bx ) + 1 );
			if ( /[┐┘─┬┼┤┴]/.test( c ) ){ s += 1 }
		}
		return s;
	},
	code2char : function( code ){
		switch ( code ){
			case  0 : return "";
			case  1 : return "";
			case  2 : return "";
			case  3 : return "─";  // "  LR"
			case  4 : return "";
			case  5 : return "┌";  // " D R"
			case  6 : return "┐";  // " DL" 
			case  7 : return "┬";  // " DLR"
			case  8 : return "";
			case  9 : return "└";  // "U  R"
			case 10 : return "┘";  // "U L "
			case 11 : return "┴";  // "U LR"
			case 12 : return "│";  // "UD  "
			case 13 : return "├";  // "UD R"
			case 14 : return "┤";  // "UDL "
			case 15 : return "┼";  // "UDLR"
			default : return "";
		}
	},
	adjust : function(){
		this.action = "adjust";
		var work = this.tbl.slice( 0 );
		var re = new RegExp( this.spcS + "{2}", "ig" );
		for ( var y = 0; y < this.tbl.length; y++ ){
			var line = this.tbl[ y ].replace( re, this.spcW );
			var ex = "";
			var bi = 0;
			while (  bi < line.blength() ){
				var ks2 = this.code2char( this.linkChk( bi, y ) );
				var s = line.bcharAt( bi );
				if ( /[─│┌┐└┘├┤┬┴┼ ]/.test( s ) && ks2.length != 0 ){
					ex += ks2;
				}else{
					ex += s;
				};
				if ( s.isHan() ){ bi += 1 }else{ bi += 2 };
			}
			work[ y ] = ex;
		}
		this.tbl = work.slice( 0 );
		var re = new RegExp( "[" + this.spcS + this.spcW + "]+$","i" );
		for ( var i = 0; i < this.tbl.length; i++ ){
			this.tbl[ i ] = this.tbl[ i ].replace( re, "" );
		}
	},
	adjust_line : function( y ){
		this.action = "adjust_line";
		var re = new RegExp( this.spcS + "{2}", "ig" );
		var line = this.tbl[ y ].replace( re, this.spcW );
		var ex = "";
		var bi = 0;
		while (  bi < line.blength() ){
			var ks2 = this.code2char( this.linkChk( bi, y ) );
			var s = line.bcharAt( bi );
			if ( /[─│┌┐└┘├┤┬┴┼ ]/.test( s ) && ks2.length != 0 ){ ex += ks2 }else{ ex += s };
			if ( s.isHan() ){ bi += 1 }else{ bi += 2 };
		}
		this.tbl[ y ] = ex;
	}
}

function ruler_action(){
	if (  tx == 1 && tx == bx && by == tblTop){
		return "makeframe"
	}else if ( tx == 1 && tx == bx && ty == tblBottom ){
		return "adjust"
	}else if ( tx == 1 && by == ty && ax == 1 ){
		return "adjust_line"
	}else if ( tx == document.GetLine( ty ).length + 1 ){
		return "vline_at_end"
	}else if ( tx != bx && ty == by ){
		return "hline"
	}
	return "vline";
}

// --------------------
// 選択範囲の記録と伸張。
var sel = Editor.ActiveDocument.Selection;
var tx = sel.GetTopPointX( mePosLogical );
var ty = sel.GetTopPointY( mePosLogical );
var bx = sel.GetBottomPointX( mePosLogical );
var by = sel.GetBottomPointY( mePosLogical );
var ax = sel.GetActivePointX( mePosLogical );
var ay = sel.GetActivePointY( mePosLogical );


sel.SetActivePoint( mePosLogical,  1, ty, false );
sel.SetActivePoint( mePosLogical,  1, by, true );
if ( ( bx == 1 )&&( ty != by ) ){
	sel.CharLeft( true, 1 );
	bx = sel.GetBottomPointX( mePosLogical );
	by--;
}else{
	sel.EndOfLine( true, mePosLogical );
};

var tblTop = ty;
var tblBottom = by;
for ( var i = 0; i <= depth; i++ ){
	var line = document.GetLine( ty - i );
	if( line.charAt( 0 )===( notch ) || ty - i <= 0 ){
		break;
	}
	tblTop = ty - i;
}
for ( var i = 0; i <= depth; i++ ){
	var line = document.GetLine( by + i );
	if( line.charAt( 0 )===( notch ) || by + i >= document.GetLines() ){
		break;
	}
	tblBottom = by + i;
}
if ( ty < tblTop ){ tblTop = ty };
if ( by > tblBottom ){ tblBottom = by};
sel.SetActivePoint( mePosLogical, 1, tblTop, false )
sel.SetActivePoint( mePosLogical, 1, tblBottom, true )
sel.EndOfLine( true, mePosLogical );

var tblruler = new TblRuler( tblTop, tblBottom, tx, ty, bx, by, document.selection.text );
switch ( ruler_action() ){
	case "makeframe":
		tblruler.makeFrame();
		break;
	case "adjust":
		tblruler.adjust();
		break;
	case "adjust_line":
		tblruler.adjust_line( ty - tblTop );
		break;
	case "hline": 
		tblruler.hline();
		break;
	case "vline_at_end":
		tblruler.vline_at_end();
		break;
	default :
		tblruler.vline();
}

sel.SetActivePoint( mePosLogical, 1, tblTop, false )
sel.SetActivePoint( mePosLogical, 1, tblBottom, true )
sel.EndOfLine( true, mePosLogical );
sel.Text = tblruler.tbl.join( "\r\n" );
sel.SetActivePoint( mePosLogical, tx, ty, false );

おまけ

  • 以下のマクロを登録して貼り付け時に使うと、入れ替えがラクになります。
// PasteWithCopy.js

var clp = ClipboardData.GetData();
ClipboardData.SetData( document.Selection.Text );
document.Selection.Text = clp;
スポンサーリンク