見出し画像

LibreOffice Calc プログラミング学習 PDF をコピーしたらレイアウトがぐちゃぐちゃになってしまったので整形した話

LibreOffice Calc をもっと勉強しようと思って、v7.0 の Calc Guide を読むことにした。全体を把握したいと思ったので、PDF を Mac のプレビューで開いて、とりあえず Contents ページだけをコピーして、LibreOffice Calc のワークシートに貼り付けた。結果、ぐちゃぐちゃになってしまったので、プログラムを書いて整形した。

処理対象の目次は、↓こんな感じ。

Copyright................................ <長いので省略> ...................................... 2

結果は↓。これは最悪のケース。

Who is this book for?.................................................................................................................8 Whats in this book?..................................................................................................................8 Where to get more help.............................................................................................................8 What you see may be different................................................................................................11 Using LibreOffice on macOS...................................................................................................11 What are all these things called?............................................................................................12 Frequently asked questions....................................................................................................13 Whats new in LibreOffice Calc 7.0?........................................................................................14

たくさんの文字が右のほうに隠れているが、ところどころ行が結合してしまった。

これを正しい形に戻したい。

Who is this book for?.................................................................................................................8
Whats in this book?..................................................................................................................8
Where to get more help.............................................................................................................8
What you see may be different................................................................................................11
Using LibreOffice on macOS...................................................................................................11
What are all these things called?............................................................................................12
Frequently asked questions....................................................................................................13
Whats new in LibreOffice Calc 7.0?........................................................................................14

↑こんな風に。

手で直しても良いが、結構面倒くさい。で、LibreOffice Basic で処理することにした。

やること

行ごとに分ける。
さらに、見出しの文字と、ページだけ取り出す。
A列に見出し、B列にページを貼り付ける。

ポイントは、見出しの文字列とページの数字を抽出するのに、正規表現を使ったこと。

ついでに、配列とセルの間でデータをやり取りする場合の配列の作り方が VBA と違うことも書いておきたい。

コード

OPTION EXPLICIT

REM  *****  BASIC  *****

Sub Main

End Sub


Sub TextFormatting()

	Dim controller As Object
	Dim MySheet As Object
	Dim MyText As String
	Dim MyRow As Integer
	Dim i As Integer
	
	Dim TextSearch As Object
	Dim SearchOptions As Object
	Dim MyResult As Variant
	
	Dim RangeCollectionWithData As Object
	Dim MyColumnRange As Object
	Dim MyRangeAddress As Variant
	Dim MyRange As Object
	Dim MyEndRow As Integer
	Dim MatchNumber As Integer
	Dim MyStartOffset As Integer
	Dim MyEndOffset As Integer
	
	Dim DataSet As Variant
	Dim DataCount As Integer
	Dim DividedText As String
	Dim MyTitle As String
	Dim MyPage As String
	
	controller = ThisComponent.getCurrentController
	MySheet = controller.getActiveSheet()
	
' TextSearch サービスのインスタンス化
	TextSearch = CreateUnoService("com.sun.star.util.TextSearch")
	SearchOptions = CreateUnoStruct("com.sun.star.util.SearchOptions")
	SearchOptions.algorithmType = com.sun.star.util.SearchAlgorithms.REGEXP
	SearchOptions.searchFlag = com.sun.star.util.SearchFlags.REG_EXTENDED
	SearchOptions.searchString = " ?(.+?)\.{2,} ?([0-9]+) ?"
	TextSearch.setOptions(SearchOptions)
	
	MyColumnRange = MySheet.getColumns().getByIndex(0)
	RangeCollectionWithData = MyColumnRange.queryContentCells(1 + 2 + 4 + 16)
			
	DataCount = 0
	
	For i = 0 To rangeCollectionWithData.getCount() - 1
	
		MyRangeAddress = RangeCollectionWithData.RangeAddresses(i)
'		MsgBox("Range No. " & i & Chr(13) & Chr(13) & _
'		"    Start Column: " & MyRangeAddress(0).StartColumn & Chr(13) & _
'		"    Start Row: " & MyRangeAddress(0).StartRow & Chr(13) & _
'		"    End Column: " & MyRangeAddress(0).EndColumn & Chr(13) & _
'		"    End Row: " & MyRangeAddress(0).EndRow)
	
		MyEndRow =  MyRangeAddress(0).EndRow
		
	Next i
	
	For MyRow = 1 To MyEndRow + 1
		MyRange = MySheet.getCellRangeByName("A" & MyRow)
		MyText = MyRange.String
		MyResult = TextSearch.searchForward(MyText, 0, Len(MyText))
	
		Do while MyResult.SubRegExpressions > 0

			MyStartOffset = MyResult.StartOffset(1)
			MyEndOffset = MyResult.EndOffset(1)
			MyTitle = Mid(MyText, MyStartOffset + 1, MyEndOffset - MyStartOffset)
			MyStartOffset = MyResult.StartOffset(2)
			MyEndOffset = MyResult.EndOffset(2)
			MyPage = Mid(MyText, MyStartOffset + 1, MyEndOffset - MyStartOffset)		
			MyEndOffset = MyResult.EndOffset(0)
			
			DividedText = DividedText & Chr(13) & "(" & MyRow & ")    " & MyTitle & ", Page " & MyPage 
		
			ReDim Preserve DataSet(DataCount)	
			DataSet(DataCount) = Array(MyTitle, MyPage)
			DataCount = DataCount + 1
			
			MyResult = TextSearch.searchForward(MyText, MyEndOffset, Len(MyText))
			
		Loop
		
	Next MyRow
	
	MyRange = MySheet.getCellRangeByName("A1:B" & (UBound(DataSet) + 1))
	MyRange.SetDataArray(DataSet)
'	MsgBox(DividedText)

End Sub


2021年4月6日記
たぶん、これ↓間違っています。インデックスの場所が変です。
MyRangeAddress = RangeCollectionWithData.RangeAddresses(i)

アクティブシートが対象。A 列の文字を処理して、A 列、B列を上書き。場合によっては元データが残るかも。

いろいろと問題があるかもしれないが、その道のプロじゃないので大目に見て欲しい。

何行あるか調べる

MyColumnRange = MySheet.getColumns().getByIndex(0)
RangeCollectionWitiData = MyColumnRange.queryContentCells(1 + 2 + 4 + 16)

A列(0列目)をレンジオブジェクトに。

queryContentCells で、データが存在するセル全てを、塊別でゲット。

1, 2, 4, 16 で、query の対象とする Content の種別を指定する。

※ query で得られたレンジを処理の対象としてもよかったと思う。

正規表現を使ってタイトルとページ表記を抽出する

SearchOptions.searchString = " ?(.+?)\.{2,} ?([0-9]+) ?"

" ?" スペースが、0個または1個

"(.+?)" タイトルを抽出しようとして作ったが、意味はよくわかっていない

"\.{2,)" リーダー部を抽出。ピリオドの2個以上連続のつもり。1個はないだろうということで2にしただけ

" ?" リーダーとページの数字の間は、スペースがある場合と直結している場合とあったので、スペース0個または1個で

"([0-9]+)" 数字の連続

丸括弧で囲んで、マッチした文字列を取り出しやすくした

マッチするもの全てを抽出できないのか

MyResult = TextSearch.searchForward(MyText, MyEndOffset, Len(MyText))

なぜか、最初の1個しかマッチしない。よく考えたらそういう仕様だった。

残りは、開始位置を変えて searchForward を再び実行。

マッチするものがなくなるまで繰り返す。

データを配列に入れる

ReDim Preserve DataSet(DataCount)	
DataSet(DataCount) = Array(MyTitle, MyPage)
DataCount = DataCount + 1

こうやって配列を作れば、setDataArray で配列をワークシートに貼り付けられる。

ただし、配列の形をワークシートに合わせなければならない。

サイズが一致していなければならない。インデックスのつけ方も。

ワークシートのセルに対応した配列は、、、

まず、1次元配列が必要。0 始まり。0 が1行目、1 が2行目、、、、

そしてその各要素に、1行分のデータを納める。納めるデータは配列でなければならない。0 始まり。0 が A 列、1 がB 列、、、、

ここにあげたプログラムでいうと、Array(MyTitle, MyPage) が1行分のデータ

Array(0) = MyTitle ・・・A列
Array(1) = MyPage ・・・B列

ということ。

配列 DataSetの インデックス 0 から最後の行に達するまで、この形の配列を入れていく。

配列をワークシートに

MyRange.SetDataArray(DataSet)

VBA と LibreOffice Basic の違い

VBA と LibreOffice Basic とでは、ワークシートのセルと配列の関係が違う。

VBA
1 始まり。2次元配列。
要素の指定は、(Row, Column)
(記憶が定かでないが、、、)

LibreOffice
0 始まり。1次元配列で、さらにその要素が配列。
要素の指定は (Row)(Column)

プログラミングの過程で出たゴミ

DividedText 処理結果を結合した文字列。最終的にワークシートに取り込んだので、今はゴミ。

反省

後で、Adobe Reader でコピーしてみたら、行が混じることはなかった。すぐに気づけばよかった。一番苦労したところが行の分割だったので、なんだかモヤモヤしてしまう。

しかし、 PDF の目次にはレベル2までしか書かれていなかった。さらに下位の項目を抜き出したかった。そこで PDF を開いて、手作業でコピーアンドペースト。まあ、必要なところだけでよいので、手作業でもよいか、、、

とか考えながらやっていたが、もう限界。面倒くさい。

そうだ、面倒なことは Python にやってもらおう。

ということで、この続きは後日、Python で PDF をいじる話として書くかもしれない。

気づいたこと

DataSet(DataCount) = Array(MyTitle, MyPage)

この1行を書く前に、結構、うろたえる出来事があった。

データが勝手に書き換えられてしまう事件

Dim ArrayA(2) てな感じで配列を定義して

ArrayA(0) = MyTitle
ArrayA(1) = MyPage

でデータを入れて

DataSet(0) = ArrayA

配列 DataSet のインデックス 0 に配列を代入。1個目のデータ処理完了。

次、

ArrayA(0) = 別のMyTitle
ArrayA(1) = 別のMyPage
DataSet(1) = ArrayA

結果、

Array(0)(0) 別のMyTitle
Array(0)(1) 別のMyPage

Array(1)(0) 別のMyTitle
Array(1)(1) 別のMyPage

Arrat(0) が書き換わっている。

なんでだあああ。

このような結果が出ることにしばらく悩んだが、参照渡しになっていることに気づき納得。

要するに

DataSet(0) = ArrayA
DataSet(1) = ArrayA

これの見たまんまだったってこと。ArrayA を書き換えたら、DataSet(0) も DataSet(1) も書き換わるということ。

VBA の感覚で行くと、= で結んだ時点で中のデータが渡されるから、それが後で変化することなんてない。しかし、LibreOffice は違った。DataSet(0) にも DataSet(1) にも、入っていたのは ArrayA のデータではなく ArrayA そのものだったのだ。

ま、そういう仕様とわかったからそれでよいけれど、値を代入するときはどうしたら良いわけ?

Python の場合、deepcopy() とかあったけど、LibreOffice の場合、何か方法はあるのかな。

結局、やりかたがわからなかったので、Array 関数でその都度作ることにした。

余談

VBA のことを書いたら思い出したことがある。

VBA の場合、多次元配列を ReDim するときは、1つの次元しか ReDim できない。ワークシートのセルと2次元配列との関係においていうと、列方向にしか ReDim できない。

なんで?

なぜ、そのような制約があるのか、不思議で仕方がない。

これに関して、OpenOffice が次のように言っている。

VBA で Preserve コマンドを使用すると、データフィールドの最終次元の上限値だけしか変更できませんが、Apache OpenOffice Basic では他の次元も変更できます。

引用元

OpenOffice ができるなら、LibreOffice もできるに違いない。やってみていないが。

LibreOffice もできるとわかった。

だいぶ前のことになるが、VBA で Web サイトの表からデータを持ってくるとき、1行を1個のデータとして扱い、データの個数の変化に合わせて配列の要素数を変えたかった。そこで、配列に配列を入れるやり方でやっていた。

しかし、それは2次元配列ではなかったので、そのままワークシートに貼り付けられず、いつも面倒くさいと思っていた。

LibreOffice の場合、そういう面倒は感じない。一方で、配列を代入するとデータではなく参照が設定されるので、VBA の感覚に慣れてしまった身には、やりにくいと感じてしまう。

t.koba




この記事が気に入ったらサポートをしてみませんか?