0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PowerPointの目次を自動生成するWSH+JavaScriptをささっと作る

0
Posted at

はじめに

PowerPoint50 頁くらいの資料を作ったところ,目次が欲しいと言われました。資料を印刷することも考慮して下記のようにページ番号 SH. と内容の一覧表があり,さらに内容のほうのセルをクリックするとそのページにジャンプするようにして欲しいというものです。

目次
SH. 内容
3 仕様案(お品書き)
4 実装コード
5 技術解説
6 結論

要するにプレゼンテーション内のハイパーリンクを貼るだけの簡単なお仕事です。ただ 10 ページなら手作業でリンクを貼れますが,50 ページとなるとやる気がしません。

PowerPointVBA マクロで自動リンク作成機能を作成しても良いのですが,マクロをアウトソーシングするのがマイブームで,WSH + JavaScript で作ることにしました。

仕様案(お品書き)

  • 現在オープンしているプレゼンテーション,すなわちアクティブプレゼンテーションを対象とします。
  • 現在選択している表を対象とします。※複数選択可能とします。
  • 表全体を選択している場合,1列目がページ番号,2列目がページの内容として,2列目に指定したページへのハイパーリンクを貼ります。
  • 表の一部(セル)を選択している場合は選択範囲内の1列目,2列目とします。1列しか選択しなかったり,3列以上選択した場合はエラーとします。
  • 1列目が数字でない場合や,プレゼンテーションの最終ページ番号よりも大きい場合,ハイパーリンクを貼らずにスキップします。
  • ついでにハイパーリンクを表示する機能,削除する機能も作ります。これは,どちらかというとテスト用ですね。

実装コード

気の短い人もいるでしょうから先に実装コードを示します。

実装コードはコチラ
pptlink.js
//---------------------------------------------------------------------------
// PpMouseActivation 列挙型
//---------------------------------------------------------------------------
var	ppMouseClick = 1;					// マウスをクリックした時に実行
var	ppMouseOver  = 2;					// マウスを通過(ホバー)した時に実行
//---------------------------------------------------------------------------
// PpSelectionType 列挙型
//---------------------------------------------------------------------------
var	ppSelectionNone   = 0;				// なし
var	ppSelectionSlides = 1;				// スライド
var	ppSelectionShapes = 2;				// 図形
var	ppSelectionText   = 3;				// テキスト
//---------------------------------------------------------------------------
// メイン関数の呼び出し
//---------------------------------------------------------------------------
var	args = WScript.Arguments.Unnamed;
var	ret = main(args);
try {
	WScript.Quit(ret);
} catch(e) {
	/* 何もしない */
}
//---------------------------------------------------------------------------
// メイン関数
//---------------------------------------------------------------------------
function main(args) {
	//-----------------------------------------------------------------------
	// ヘルプメッセージ
	//-----------------------------------------------------------------------
	if(args.Count == 0) {
		var	msg = [
			"PowerPoint のテキストボックスのハイパーリンクユーティリティ",
			"",
			"PPTLINK(.JS) [コマンド]",
			"",
			"コマンド:",
			"    list: ハイパーリンク一覧を表示します。",
			"  remove: ハイパーリンクを削除します。",
			"   table: テーブルに目次を作成します。",
			"",
			"現在選択しているテキストボックスを対象とします。"
		];
		WScript.StdErr.WriteLine(msg.join("\n"));
		return -1;
	}
	//-----------------------------------------------------------------------
	// コマンドのチェック
	//-----------------------------------------------------------------------
	var	func_table = {
		  list: command_list,
		remove: command_remove,
		 table: command_table
	};
	var	func = func_table[args(0).toLowerCase()];
	if(func == null) {
		WScript.StdErr.WriteLine("コマンド " + args(0) + " には対応していません!!");
		return -1;
	}
	//-----------------------------------------------------------------------
	// PowerPoint の起動
	//-----------------------------------------------------------------------
	var	app = null;
	try {
		app = GetObject("", "PowerPoint.Application");
	} catch(e) {
		app = WScript.CreateObject("PowerPoint.Application");
	}
	if(app == null) {
		WScript.StdErr.WriteLine("PowerPoint の起動に失敗しました!!");
		return -1;
	}
	app.Visible = true;
	//-----------------------------------------------------------------------
	// アクティブプレゼンテーションのチェック
	//-----------------------------------------------------------------------
	if(app.Presentations.Count == 0) {
		WScript.StdErr.WriteLine("アクティブプレゼンテーションが見つかりません!!");
		return -1;
	}
	//-----------------------------------------------------------------------
	// 現在選択しているオブジェクトのチェック ⇒ テキストボックス以外はエラー
	//-----------------------------------------------------------------------
	var	sel = app.ActiveWindow.Selection;
	switch(sel.Type) {
	  case ppSelectionShapes:
	  case ppSelectionText:
		break;
	  default:
		WScript.StdErr.WriteLine("テキストボックスが選択されていません!!");
		return -1;
	}
	//-----------------------------------------------------------------------
	// 現在選択しているテキストボックスに対して実行
	//-----------------------------------------------------------------------
	for(var i = 1; i <= sel.ShapeRange.Count; i++)
		func(sel.ShapeRange(i));
	return 0;
}
//---------------------------------------------------------------------------
// ハイパーリンク一覧の作成
//---------------------------------------------------------------------------
function get_list(shape) {
	var	list = [];
	var	check_range = function(range) {
		var	link = range.ActionSettings(ppMouseClick).Hyperlink;
		if(link.Address == "" && link.SubAddress == "") return false;
		list.push({ text:range.Text, link:link });
		return true;
	};
	var	check_shape = function(shape) {
		if(!shape.HasTextFrame) return;
		if(!shape.TextFrame.HasText) return;
		var	range = shape.TextFrame.TextRange;
		if(check_range(range)) return;
		for(var i = 1; i <= range.Runs().Count; i++)
			check_range(range.Runs(i));
	};
	if(shape.HasTable) {
		var	table = shape.Table;
		for(var i = 1; i <= table.Rows.Count; i++ )
			for(var j = 1; j <= table.Columns.Count; j++ )
				check_shape(table.Cell(i, j).Shape);
	} else {
		check_shape(shape);
	}
	return list;
}
//---------------------------------------------------------------------------
// コマンド list
//---------------------------------------------------------------------------
function command_list(shape) {
	//-------------------------------------------------------------------
	// ハイパーリンク一覧の作成
	//-------------------------------------------------------------------
	var	list = get_list(shape);
	if(list.length == 0) return;
	//-------------------------------------------------------------------
	// ハイパーリンク一覧表示
	//-------------------------------------------------------------------
	for(var i = 0; i < list.length; i++) {
		var	a = [
			"\"" + list[i].text            + "\"",
			"\"" + list[i].link.Address    + "\"",
			"\"" + list[i].link.SubAddress + "\""
		];
		WScript.Echo(a.join(","));
	};
	WScript.Echo(list.length + " 個のハイパーリンクがあります。");
}
//---------------------------------------------------------------------------
// コマンド remove
//---------------------------------------------------------------------------
function command_remove(shape) {
	//-------------------------------------------------------------------
	// ハイパーリンク一覧の作成
	//-------------------------------------------------------------------
	var	list = get_list(shape);
	if(list.length == 0) return;
	//-------------------------------------------------------------------
	// ハイパーリンクの削除
	//-------------------------------------------------------------------
	for(var i = 0; i < list.length; i++) {
		var	a = [
			"\"" + list[i].text            + "\"",
			"\"" + list[i].link.Address    + "\"",
			"\"" + list[i].link.SubAddress + "\""
		];
		WScript.Echo("削除: " + a.join(","));
		list[i].link.Delete();
	}
	WScript.Echo(list.length + " 個のハイパーリンクを削除しました。");
}
//---------------------------------------------------------------------------
// コマンド table
//---------------------------------------------------------------------------
function command_table(shape) {
	if(!shape.HasTable) return;
	var	table = shape.Table;
	if(table.Columns.Count < 2) {
		WScript.StdErr.WriteLine("二列以上ある表を選択して下さい!!")
		return;
	}
    //-------------------------------------------------------------------
	// 選択しているセル範囲を調べる
	//-------------------------------------------------------------------
	var	row1 = 0, row2 = 0, col1 = 0, col2 = 0;
	for(var i = 1; i <= table.Rows.Count; i++) {
		for(var j = 1; j <= table.Columns.Count; j++) {
			if(!table.Cell(i, j).Selected) continue;
			if(row1 < 1) row1 = i;
			if(col1 < 1) col1 = j;
			if(row2 < i) row2 = i;
			if(col2 < j) col2 = j;
		}
	}
	if(row1 < 1) {
		row1 = 1; row2 = table.Rows.Count;
		col1 = 1; col2 = 2;
	} else if(col2 - col1 != 1) {
		WScript.StdErr.WriteLine("セルを二列選択して下さい!!")
		return;
	}
	//-------------------------------------------------------------------
	// ハイパーリンク一覧の作成
	//-------------------------------------------------------------------
	var	app = shape.Application;
	var	last = app.ActivePresentation.Slides.Count;
	var	count = 0;
	for(var i = row1; i <= row2; i++) {
		var	range1 = table.Cell(i, col1).Shape.TextFrame.TextRange;
		var	range2 = table.Cell(i, col2).Shape.TextFrame.TextRange;
		var	s = range1.Text
		var	m = s.match(/^\s*(\d+)\s*$/);
		if(m == null) continue;
		var	n = parseInt(m[1]);
		if(n < 1) continue;
		if(n > last) continue;
		var	link = range2.ActionSettings(ppMouseClick).Hyperlink;
		link.Address = "";
		link.SubAddress = n;
		var	a = [
			"\"" + range2.Text + "\"", n
		];
		WScript.Echo("追加: " + a.join(","));
		count++;
	}
	WScript.Echo(count + " 個のハイパーリンクを作成しました。");
}

技術解説

0. コマンドのチェック

第一引数 args(0) でコマンドを与えます。これまではC言語のように switchcase で分岐していましたが,JavaScript っぽく連想配列を使うことにしました。

コマンドのチェック
var	func_table = {
	  list: command_list,
	remove: command_remove,
	 table: command_table
};
var	func = func_table[args(0).toLowerCase()];
if(func == null) {
	WScript.StdErr.WriteLine("コマンド " + args(0) + " には対応していません!!");
	return -1;
}

1. PowerPoint の起動

既に PowerPoint を起動していればそのアプリケーションオブジェクトを流用します。そうでなければ新たに起動します。最後に Visible プロパティを true にしておく必要があります。これを忘れると大量のゾンビプロセスを発生させることになります。

PowerPointの起動
var	app = null;
try {
	app = GetObject("", "PowerPoint.Application");
} catch(e) {
	app = WScript.CreateObject("PowerPoint.Application");
}
if(app == null) {
	WScript.StdErr.WriteLine("PowerPoint の起動に失敗しました!!");
	return -1;
}
app.Visible = true;

2. アクティブプレゼンテーションのチェック

アクティブプレゼンテーションの存在チェックです。現在オープンしているプレゼンテーションの数がゼロかどうかで判別するようにしました。まったくプレゼンテーションを開いていない状況で ActivePresentation を参照するとnull を返すのではなく例外を引き起こすので。

アクティブプレゼンテーションのチェック
if(app.Presentations.Count == 0) {
	WScript.StdErr.WriteLine("アクティブプレゼンテーションが見つかりません!!");
	return -1;
}

3. 現在選択しているオブジェクトのチェック

現在選択しているオブジェクトのチェックです。テキストボックス以外はエラーとします。

現在選択しているオブジェクトのチェック
var	sel = app.ActiveWindow.Selection;
switch(sel.Type) {
  case ppSelectionShapes:
  case ppSelectionText:
	break;
  default:
	WScript.StdErr.WriteLine("テキストボックスが選択されていません!!");
	return -1;
}

4. コマンドの実行

現在選択しているテキストボックス(複数可)に対してコマンドを実行します。

コマンドの実行
for(var i = 1; i <= sel.ShapeRange.Count; i++)
	func(sel.ShapeRange(i));

5. list コマンド

指定した図形オブジェクト shape のハイパーリンクを表示します。実はテキストボックスのハイパーリンクを取得するのは少々面倒なので get_list() を呼び出します。関数化したのは remove コマンドと共用する意味もあります。

listコマンド
function command_list(shape) {
	var	list = get_list(shape);
	if(list.length == 0) return;
	for(var i = 0; i < list.length; i++) {
		var	a = [
			"\"" + list[i].text            + "\"",
			"\"" + list[i].link.Address    + "\"",
			"\"" + list[i].link.SubAddress + "\""
		];
		WScript.Echo(a.join(","));
	};
	WScript.Echo(list.length + " 個のハイパーリンクがあります。");
}

6. removeコマンド

指定した図形オブジェクト shape のハイパーリンクを削除します。Microsoft Office のコレクションを削除する場合,最後の要素から削除したり,あるいはいったん別の配列にオブジェクトをコピーしてから削除するなどの工夫が必要です。このため関数 get_list() を呼び出してハイパーリンクの配列を取得してから削除します。

removeコマンド
function command_remove(shape) {
	var	list = get_list(shape);
	if(list.length == 0) return;
	for(var i = 0; i < list.length; i++) {
		var	a = [
			"\"" + list[i].text            + "\"",
			"\"" + list[i].link.Address    + "\"",
			"\"" + list[i].link.SubAddress + "\""
		];
		WScript.Echo("削除: " + a.join(","));
		list[i].link.Delete();
	}
	WScript.Echo(list.length + " 個のハイパーリンクを削除しました。");
}

7. ハイパーリンク一覧の取得

先ほど少々面倒だといったハイパーリンクを取得する関数です。

  • 表の場合はセルごとに分割して取得する必要がありますが,これ自体はさほど難解ではありません。
  • テキストボックスの文字列全体にリンクが貼られている場合もあれば,文字列の一部のみにリンクが貼られている場合もあります。テキストボックスの文字列全体にリンクが貼られている場合,分割された文字列にもリンクが存在するため,二重にカウントしないように注意する必要があります。一方,文字列の一部のみにリンクが貼られている場合,全体のリンクは存在しません。
ハイパーリンク一覧の作成
function get_list(shape) {
	var	list = [];
	var	check_range = function(range) {
		var	link = range.ActionSettings(ppMouseClick).Hyperlink;
		if(link.Address == "" && link.SubAddress == "") return false;
		list.push({ text:range.Text, link:link });
		return true;
	};
	var	check_shape = function(shape) {
		if(!shape.HasTextFrame) return;
		if(!shape.TextFrame.HasText) return;
		var	range = shape.TextFrame.TextRange;
		if(check_range(range)) return;
		for(var i = 1; i <= range.Runs().Count; i++)
			check_range(range.Runs(i));
	};
	if(shape.HasTable) {
		var	table = shape.Table;
		for(var i = 1; i <= table.Rows.Count; i++ )
			for(var j = 1; j <= table.Columns.Count; j++ )
				check_shape(table.Cell(i, j).Shape);
	} else {
		check_shape(shape);
	}
	return list;
}

8. table コマンド

このコマンドだけは指定した図形が表(テーブル)以外の場合は何もしないでリターンします。

  • 表全体を選択した場合,表の1列目がページ番号,2列目がページの内容として2列目にハイパーリンクを貼りますが,1列目の内容をパースして空白だったり,整数に変換できない場合は次行にスキップします。またページ番号が範囲外のときも次行にスキップします。※エラーとはしません。
  • 表の一部を選択したとき,選択範囲の1列目をページ番号,2列目をページの内容と判断します。1列しか選択しなかったり,3列以上選択した場合はエラーとします。
  • PowerPoint には表の選択範囲を一発で取得できる便利な機能はありません。表のすべてのセルを検索して Selected プロパティが有効になっていれば現在選択されている範囲の一部ということになります。このため表全体を検索してSelected プロパティが有効になっているセルの行番号と列番号の最小値と最大値をそれぞれ求めます。
       
  3 仕様案(お品書き)      
  4 実装コード      
  5 技術解説      
  6 結論      
       
table コマンド
function command_table(shape) {
    if(!shape.HasTable) return;
	var	table = shape.Table;
	if(table.Columns.Count < 2) {
		WScript.StdErr.WriteLine("二列以上ある表を選択して下さい!!")
		return;
	}
	var	row1 = 0, row2 = 0, col1 = 0, col2 = 0;
	for(var i = 1; i <= table.Rows.Count; i++) {
		for(var j = 1; j <= table.Columns.Count; j++) {
			if(!table.Cell(i, j).Selected) continue;
			if(row1 < 1) row1 = i;
			if(col1 < 1) col1 = j;
			if(row2 < i) row2 = i;
			if(col2 < j) col2 = j;
		}
	}
	if(row1 < 1) {
		row1 = 1; row2 = table.Rows.Count;
		col1 = 1; col2 = 2;
	} else if(col2 - col1 != 1) {
		WScript.StdErr.WriteLine("セルを二列選択して下さい!!")
		return;
	}
	var	app = shape.Application;
	var	last = app.ActivePresentation.Slides.Count;
	var	count = 0;
	for(var i = row1; i <= row2; i++) {
		var	range1 = table.Cell(i, col1).Shape.TextFrame.TextRange;
		var	range2 = table.Cell(i, col2).Shape.TextFrame.TextRange;
		var	s = range1.Text
		var	m = s.match(/^\s*(\d+)\s*$/);
		if(m == null) continue;
		var	n = parseInt(m[1]);
		if(n < 1) continue;
		if(n > last) continue;
		var	link = range2.ActionSettings(ppMouseClick).Hyperlink;
		link.Address = "";
		link.SubAddress = n;
		var	a = [
			"\"" + range2.Text + "\"", n
		];
		WScript.Echo("追加: " + a.join(","));
		count++;
	}
	WScript.Echo(count + " 個のハイパーリンクを作成しました。");
}

実行例

引数なしで実行するとヘルプメッセージを表示します。

PowerPoint のテキストボックスのハイパーリンクユーティリティ

PPTLINK(.JS) [コマンド]

コマンド:
    list: ハイパーリンク一覧を表示します。
  remove: ハイパーリンクを削除します。
   table: テーブルに目次を作成します。

現在選択しているテキストボックスを対象とします。

サンプルのプレゼンテーションです。

上記の表を選択して table コマンドを実行します。

table コマンドの実行結果
c:\Qiita>pptlink table
追加: "仕様案(お品書き)",3
追加: "実装コード",4
追加: "技術解説",5
追加: "結論",6
4 個のハイパーリンクを作成しました。

実行すると下記のようにハイパーリンクが貼られます。先頭行と最終行にはリンクが貼られません。

list コマンドの実行結果を示します。表示内容は(おそらく)下記の意味だと思われます。

  • ハイパーリンクを貼った文字列
  • ハイパーリンク先のアドレス ※今回はローカルなので空文字
  • ハイパーリンク先のサブアドレス
    • スライドID
    • ページ番号
    • スライドのタイトル文字列
list コマンドの実行結果
c:\Qiita>pptlink list
"仕様案(お品書き)","","258,3,[3] 仕様案(お品書き)"
"実装コード","","259,4,[4] 実装コード"
"技術解説","","260,5,[5] 技術解説"
"結論","","261,6,[6] 結論"
4 個のハイパーリンクがあります。

remove コマンドの実行結果を示します。

remove コマンドの実行結果
c:\Qiita>pptlink remove
削除: "仕様案(お品書き)","","258,3,[3] 仕様案(お品書き)"
削除: "実装コード","","259,4,[4] 実装コード"
削除: "技術解説","","260,5,[5] 技術解説"
削除: "結論","","261,6,[6] 結論"
4 個のハイパーリンクを削除しました。

結言というか感想

このスクリプトを次に使うのっていつになるのかなあ?

エラッタ

下記の不具合というか課題があることが分かっています。

  • テキストボックスがグループ化されているとハイパーリンクを取得できません。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?