Wie füge ich einer LibreOffice Calc-Datei ein Inhaltsverzeichnis hinzu?

771
tohuwawohu

Das Hinzufügen eines ToC in LO Writer ist kein Problem, aber wie fügt man einen ToC in eine odsDatei ein? Wenn eine Arbeitsmappe mit Tabellen, die länger als eine Seite sind, als Ausdruck (nicht als Datei) verteilt werden soll, wäre es schön, einen ToC auf dem ersten Blatt zu haben, der alle anderen Blätter in derselben odsDatei mit Seitennummern auflistet .

Ich habe versucht, ein Writer-OLE-Objekt einzufügen, das das Hinzufügen eines ToC (innerhalb des OLE-Objekts ...) ermöglicht. Das Objekt scheint jedoch die Überschriften der anderen Blätter zu ignorieren. Das Einfügen der Blattnamen mit Hyperlinks wäre in Ordnung, aber ich habe keine Möglichkeit gefunden, auch die Seitenzahlen einzufügen.

Wenn dies ein Makro erfordert (StarBasic bevorzugt), biete ich eine Prämie an.

Irgendwelche Ideen?

PS: Ich habe in den OpenOffice.org-Foren aus dem Jahr 2008 ein Q / A gefunden, aber ich verstehe nicht, wie ich es implementieren kann ...

3
Die Herausforderung dabei ist, dass Seitennummern nicht wie in Writer Teil von Kalkulationstabellen sind. Ich arbeite an einem Makro, das auf nicht leeren Seiten nach [Seitenumbrüchen] (https://wiki.openoffice.org/wiki/Documentation/DevGuide/Spreadsheets/Spreadsheet#Page_Breaks) sucht. Wir werden also sehen, wie es funktioniert geht Jim K vor 7 Jahren 0
@JimK: danke für dein feedback - ich werde morgen eine Kopfprämie hinzufügen (frage ist noch nicht für eine Kopfprämie geeignet). Da die Kopf- / Fußzeile ein Seitennummernfeld enthalten kann, frage ich mich, ob das auch im Blatt selbst verfügbar ist ... tohuwawohu vor 7 Jahren 0
Die Kopf- / Fußzeile verwendet die XML-Tag, das auch Writer für Einfügen -> Feld verwendet. Ich habe versucht, dies in den Rumpf einer Calc-Tabelle (content.xml) einzufügen, aber das Tag wurde ignoriert. Jim K vor 7 Jahren 0
Für die von Ihnen verlinkte Seite scheint es ratsam zu sein, Blöcke zu erstellen und _navigate_ mit F5 zu erstellen ... Ich habe den "Gliederungsteil" nicht verstanden. Trotzdem kann die Idee nützlich sein. Vielleicht finden Sie einige Anregungen in [pitonyak] (http://www.pitonyak.org/AndrewMacro.odt), 7.18, oder Sie können einen Zyklus für die Suche nach den Merkmalen _heading_ und _heading1_ in der anderen Tabelle (möglicherweise nur in der ersten Spalte) ausführen ) oder Blöcke und Druckbereiche. Tricky die Seitennummer, möglicherweise aus der Druckbereichsnummer berechnet. Sorry nur Ideen, nicht mehr ... Hastur vor 7 Jahren 0

2 Antworten auf die Frage

3
Jim K

Ok, hier ist der Code, den ich mir ausgedacht habe:

Type PageBreakLocation Row As Long Col As Long Sheet As Long End Type  Function GetLocationKey(item As PageBreakLocation) GetLocationKey = "s" & item.Sheet & "r" & item.Row & "c" & item.Col End Function  Type PageOfSheet Sheet As Long Page As Long End Type  Sub CalcTableOfContents used_pages = FindAllUsedPages() page_of_each_sheet = GetPageOfEachSheet(used_pages) Insert_TOC(page_of_each_sheet) DisplayContents(page_of_each_sheet) End Sub  Sub DisplayContents(page_of_each_sheet As Collection) msg = "" For Each value In page_of_each_sheet sheet_name = ThisComponent.Sheets.getByIndex(value.Sheet).getName() msg = msg & "Sheet(" & value.Sheet & ") """ & sheet_name & _ """ .....Page " & value.Page & CHR(13) Next MsgBox msg End Sub  ' Insert a Table of Contents into sheet 1. Sub Insert_TOC(page_of_each_sheet As Collection) oSheet = ThisComponent.Sheets.getByIndex(0) oCell = oSheet.getCellByPosition(1, 1) 'B2 oCell.SetString("Table of Contents") row = 3 ' the fourth row For Each value In page_of_each_sheet oCell = oSheet.getCellByPosition(1, row) ' column B oCell.SetString(ThisComponent.Sheets.getByIndex(value.Sheet).getName()) oCell = oSheet.getCellByPosition(3, row) ' column D oCell.SetString("Page " & value.Page) row = row + 1 Next End Sub  ' Returns a collection with key as sheet number and item as page number. Function GetPageOfEachSheet(used_pages As Collection) Dim page_of_each_sheet As New Collection page_number = 1 For Each used_page In used_pages key = CStr(used_page.Sheet) If Not Contains(page_of_each_sheet, key) Then Dim value As New PageOfSheet value.Sheet = used_page.Sheet value.Page = page_number page_of_each_sheet.Add(value, key) End If page_number = page_number + 1 Next GetPageOfEachSheet = page_of_each_sheet End Function  ' Looks through all used cells and adds those pages. ' Returns a collection of used pages. Function FindAllUsedPages Dim used_pages As New Collection For Each addr in GetFilledRanges() FindPagesForRange(addr, used_pages) Next FindAllUsedPages = used_pages End Function  ' Returns an array of filled cells. ' Elements are type com.sun.star.table.CellRangeAddress. ' Note: oSheet.getPrintAreas() seemed like it might do this, but in testing, ' it always returned empty. Function GetFilledRanges allRangeResults = ThisComponent.createInstance( _ "com.sun.star.sheet.SheetCellRanges") For i = 0 to ThisComponent.Sheets.getCount() - 1 oSheet = ThisComponent.Sheets.getByIndex(i) With com.sun.star.sheet.CellFlags printable_content = .VALUE + .DATETIME + .STRING + .ANNOTATION + _ .FORMULA + .OBJECTS End With filled_cells = oSheet.queryContentCells(printable_content) allRangeResults.addRangeAddresses(filled_cells.getRangeAddresses(), False) Next ' Print allRangeResults.getRangeAddressesAsString() GetFilledRanges = allRangeResults.getRangeAddresses() End Function  ' Looks through the range and adds any pages to used_pages. ' Note: row.IsStartOfNewPage is only for manual breaks, so we do not use it. Sub FindPagesForRange(range As Object, used_pages As Collection) oSheet = ThisComponent.Sheets.getByIndex(range.Sheet) aPageBreakArray = oSheet.getRowPageBreaks() Dim used_row_breaks() As Variant Dim used_col_breaks() As Variant prev_break_row = 0 For nIndex = 0 To UBound(aPageBreakArray()) break_row = aPageBreakArray(nIndex).Position If break_row = range.StartRow Then Append(used_row_breaks, break_row) ElseIf break_row > range.StartRow Then Append(used_row_breaks, prev_break_row) End If If break_row > range.EndRow Then Exit For End If prev_break_row = break_row Next prev_break_col = 0 aPageBreakArray = oSheet.getColumnPageBreaks() For nIndex = 0 To UBound(aPageBreakArray()) break_col = aPageBreakArray(nIndex).Position If break_col = range.StartColumn Then Append(used_col_breaks, break_col) ElseIf break_col > range.StartColumn Then Append(used_col_breaks, prev_break_col) End If If break_col > range.EndColumn Then Exit For End If prev_break_col = break_col Next For Each row In used_row_breaks() For Each col In used_col_breaks() Dim location As New PageBreakLocation location.Sheet = range.Sheet location.Row = row location.Col = col key = GetLocationKey(location) If Not Contains(used_pages, key) Then used_pages.Add(location, key) End If Next col Next row End Sub  ' Returns True if the collection contains the key, otherwise False. Function Contains(coll As Collection, key As Variant) On Error Goto ErrorHandler coll.Item(key) Contains = True Exit Function ErrorHandler: If Err <> 5 Then MsgBox "Error " & Err & ": " & Error$ & " (line : " & Erl & ")" End If Contains = False End Function  ' Append an element to an array, increasing the array's size by 1. Sub Append(array() As Variant, new_elem As Variant) old_len = UBound(array) ReDim Preserve array(old_len + 1) As Variant array(old_len + 1) = new_elem End Sub 

Es ist wahrscheinlich eine gute Idee, diesen Code in ein eigenes Modul zu schreiben, da er so groß ist. Um es auszuführen, gehen Sie zur Routine Tools -> Macros -> Run Macround führen Sie sie aus CalcTableOfContents.

Damit es die richtigen Seitenzahlen erhält, gibt es einen wichtigen Trick. Der Code prüft nur die Seitennummer jeder Zelle. Wenn sich also der Inhalt einer Zelle auf zwei Seiten kreuzt, wird nur die erste Seite gezählt.

Fügen Sie in einer Zelle auf der zweiten Seite Inhalt hinzu, um dieses Problem zu beheben. Setzen Sie es auf "Nicht druckbar Format -> Cells -> Cell Protection", indem Sie "Ausblenden beim Drucken" auswählen. Dadurch wird das Makro gezwungen, die zweite Seite zu erkennen.

Wenn alles gut geht, sollte es auf Blatt 1 so aussehen:

Calc Inhaltsverzeichnis

Credits:

Whooaa - sieht gut aus! Ich brauche etwas Zeit, um es gründlich zu überprüfen, aber es scheint zu tun, was es soll. Es gibt nur einige kleinere Punkte - in ToC würde ich die "echten" Blattnamen anstelle von "Blatt 1", "Blatt 2" usw. bevorzugen. Und manchmal scheint es ein Blatt zu fehlen. Aber die Antwort ist definitiv die Belohnung wert, da es scheinbar keine anderen Antworten gibt ... tohuwawohu vor 7 Jahren 0
Um den Blattnamen anstelle der Nummer zu erhalten, können Sie in die Routine `Insert_TOC`` blattname = ThisComponent.Sheets.getByIndex (sheet - 1) .getName () `schreiben. Seltsam, dass es manchmal ein Blatt fehlen würde. Gibt es auf allen Blättern mindestens eine Zelle mit druckfähigem Inhalt? Jim K vor 7 Jahren 0
Die Einstellung des Blattnamens funktioniert gut - ich habe mir die Freiheit genommen, Ihre Antwort zu bearbeiten, um diese Änderung zu implementieren (hoffe, das ist in Ordnung ...) Zu den fehlenden Einträgen: Es scheint zu funktionieren **, wenn jedes Blatt mehr als eine Seite umfasst ** . Wenn Blätter auf eine einzige Seite passen, werden einige Blätter ignoriert. tohuwawohu vor 7 Jahren 1
Ich hätte es beim ersten Mal richtig funktionieren können, wenn Sie mir erlaubt hätten, Python zu verwenden. :) Wie auch immer, ich habe einige Fehler behoben und die Antwort aktualisiert, also hoffentlich klappt es jetzt. Jim K vor 7 Jahren 0
0
tohuwawohu

Hier ist ein anderer Ansatz. Ich fragte mich, ob es eine Möglichkeit gibt, die Seitenumbrüche mithilfe von zu bestimmen IsStartOfNewPage. Dies funktioniert, nachdem LO Calc die Seitenumbrüche berechnet hat, indem in die PageBreak-Ansicht und zurück gewechselt wird. Das Zählen von Seiten ist jetzt recht einfach, indem alle verwendeten Zellen durchlaufen werden (mit dem aktuellen Blatt Cursorund GotoEndOfUsedArea).

Ich habe nicht getestet, ob Zellen, die sich über mehrere Seiten erstrecken, zu einer falschen Seitenzahl führen. Ich gehe außerdem davon aus, dass der resultierende ToC niemals mehr als eine Seite dauern wird.

Option Base 0 Option Explicit  Private Type SheetInformation SheetIndex As Long SheetName As String PageStart as Long PageEnd as Long PageCount As Long End Type  Public Sub Calc_ToC  If (False = IsSpreadsheetDoc(ThisComponent)) Then MsgBox "Works only for spreadsheets!" Exit Sub End If ThisComponent.LockControllers  Dim mySheets(ThisComponent.Sheets.getCount() - 1) As New SheetInformation Dim origSheet As Long origSheet = ThisComponent.getCurrentController.ActiveSheet.RangeAddress.Sheet  Call collectSheetInfo(mySheets)  dim document as Object dim dispatcher as Object document = ThisComponent.CurrentController.Frame dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")  dim args1(0) as new com.sun.star.beans.PropertyValue args1(0).Name = "Nr" args1(0).Value = origSheet + 1 dispatcher.executeDispatch(document, ".uno:JumpToTable", "", 0, args1())  ThisComponent.unlockControllers()  Call insertToc(mySheets)  End Sub  Private Sub collectSheetInfo(allSheetsInfo() as New SheetInformation) Dim i As Long Dim maxPage As Long maxPage = 0  For i = 0 To UBound(allSheetsInfo) Dim sheetInfo As New SheetInformation sheetInfo.SheetIndex = i sheetInfo.SheetName = ThisComponent.Sheets.getByIndex(sheetInfo.SheetIndex).getName() Call getPageCount(sheetInfo) sheetInfo.PageStart = maxPage + 1 sheetInfo.PageEnd = sheetInfo.PageStart + sheetInfo.PageCount - 1 maxPage = sheetInfo.PageEnd allSheetsInfo(i) = sheetInfo Next  End Sub  Private Sub getPageCount(s As SheetInformation) Dim oSheet, oCell, oCursor As Object Dim i, j, pageCount As Long Dim isHorizontalPageBreak, isVerticalPageBreak As Boolean  dim document as Object dim dispatcher as Object document = ThisComponent.CurrentController.Frame dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")  dim args1(0) as new com.sun.star.beans.PropertyValue args1(0).Name = "Nr" args1(0).Value = s.SheetIndex + 1 dispatcher.executeDispatch(document, ".uno:JumpToTable", "", 0, args1())  args1(0).Name = "PagebreakMode" args1(0).Value = true dispatcher.executeDispatch(document, ".uno:PagebreakMode", "", 0, args1()) dim args2(0) as new com.sun.star.beans.PropertyValue args2(0).Name = "NormalViewMode" args2(0).Value = true dispatcher.executeDispatch(document, ".uno:NormalViewMode", "", 0, args2())  oSheet = ThisComponent.Sheets.getByIndex(s.SheetIndex)  oCursor = oSheet.createCursor oCursor.GotoEndOfUsedArea(True)  pageCount = 1  For i=0 To oCursor.RangeAddress.EndColumn For j=0 To oCursor.RangeAddress.EndRow oCell = oSheet.GetCellByPosition(i,j) isHorizontalPageBreak = Abs(cINT(oCell.Rows.getByIndex(0).IsStartOfNewPage)) isVerticalPageBreak = Abs(cINT(oCell.Columns.getByIndex(0).IsStartOfNewPage)) If i = 0 Then If isHorizontalPageBreak Then pageCount = pageCount + 1 End If ElseIf j = 0 Then If isVerticalPageBreak Then pageCount = pageCount + 1 End If Else If (isHorizontalPageBreak AND isVerticalPageBreak) Then pageCount = pageCount + 1 End if End if Next j Next i s.pageCount = pageCount  End Sub  ''' ------------------------------------------------------------- ''' IsSpreadsheetDoc - Check if current document is a calc file ''' ------------------------------------------------------------- ''' Source: "Useful Macro Information For OpenOffice.org By ''' Andrew Pitonyak", Ch. 6.1 ''' ------------------------------------------------------------- Private Function IsSpreadsheetDoc(oDoc) As Boolean Dim s$ : s$ = "com.sun.star.sheet.SpreadsheetDocument" On Local Error GoTo NODOCUMENTTYPE IsSpreadsheetDoc = oDoc.SupportsService(s$) NODOCUMENTTYPE: If Err <> 0 Then IsSpreadsheetDoc = False Resume GOON GOON: End If End Function  Private Sub Result(s() As SheetInformation) Dim msg As String Dim i As Integer Dim obj As SheetInformation msg = ""  For i = 0 To UBound(s) obj = s(i) With obj msg = msg & .SheetName & " (Index: " & .SheetIndex & _ ") - Pages: " & .PageCount & _ " - from/to: " & .PageStart & "/" & .PageEnd & CHR(13) End With Next MsgBox(msg) End Sub  Private Sub insertToC(s() As SheetInformation)  Select Case MsgBox("Insert ToC on cursor position?" & CHR(10) & _ "(Yes: Insert at cursor; No: stop macro)", 36) Case 6 'Yes - insert at cursor position' Call DoInsert(s) Case 7 'No - insert on new sheet' ThisComponent.unlockControllers() Exit Sub End Select End Sub  Private Sub DoInsert(s() As SheetInformation)  Dim oSheet, oCell, startCell As Object Dim sheet,rowStart, colStart, row, col, start As Long Dim sName As String Dim currentSheet As SheetInformation Dim newToc As Boolean  oSheet = ThisComponent.getCurrentController.ActiveSheet startCell = ThisComponent.getCurrentSelection()  oCell = startCell rowStart = startCell.CellAddress.Row colStart = startCell.CellAddress.Column oCell.SetString("Table of Contents") For sheet = 1 to Ubound(s) + 1 currentSheet = s(sheet - 1) row = rowStart + sheet oCell = oSheet.getCellByPosition(colStart, row) ' column B oCell.SetString(currentSheet.SheetName) oCell = oSheet.getCellByPosition(colStart + 2, row) ' column D start = currentSheet.PageStart  oCell.SetString("Page " & start) Next ThisComponent.unlockControllers() End Sub 

Ich habe einige Beispiel - Code von Andrew Pitonyak ( „verwendet Nützliche Makro Informationen für OpenOffice.org Von Andrew Pitonyak (ODT) ‚und‘ OpenOffice.org Makros Erklärung (PDF) “) und von Villeroy Cell Selbstbeobachtung Modul und natürlich einige JimKs Lösung .

BEARBEITEN:

Das Makro testet nicht jede Seite, wenn es druckbaren Inhalt enthält. Es wird lediglich davon GotoEndOfUsedAreaausgegangen, dass der gesamte "verwendete" Zellbereich (identifiziert mit ) beim Erstellen des ToC berücksichtigt werden sollte. Daher können leere Seiten als zu druckende Seiten gezählt werden. Daher kann es bei dünn gefüllten Blättern zu schlechten Ergebnissen kommen. Ich hoffe jedoch, dass es sich in den meisten Fällen, in denen keine leeren Seiten vorhanden sind, zuverlässiger verhält.

Es wird also davon ausgegangen, dass die folgenden Blätter auf sechs Seiten gedruckt werden, auch wenn eine Seite (ohne X) leer bleiben kann:

+-+-+ +-+-+ +-+-+ |X|X| |X|X| |X| | +-+-+ +-+-+ +-+-+ |X| | | |X| | | | +-+-+ +-+-+ +-+-+ |X|X| |X|X| | |X| +-+-+ +-+-+ +-+-+ 
Interessanterweise habe ich `IsStartOfNewPage` vermieden, weil es [hier] (https://www.openoffice.org/api/docs/common/ref/com/sun/star/table/TableRow.html#IsStartOfNewPage) besagt, dass dies nur der Fall ist für manuelle Pausen. Ihr Makro scheint jedoch zumindest teilweise zu funktionieren. Mein Testdokument hat 13 Seiten, wenn es als PDF gedruckt wird, aber dieses Makro zeigt das letzte Blatt ab Seite 20. Zum einen zählt es ein leeres Blatt, das nicht wirklich in der PDF-Datei gedruckt wurde, sondern nur eine Seite berücksichtigt aus. Ich werde versuchen, es noch einmal zu testen und zu sehen, was sonst Probleme verursacht. Jim K vor 7 Jahren 0
Ok, nachdem Sie sich Ihren Code etwas genauer angesehen haben, haben wir beide gute Ideen. Ich habe auch das andere Problem gefunden, das Ihr Code mit meinem Testdokument hat. Derzeit geht getPageCount () davon aus, dass sich Seiten mit Inhalt immer in einem rechteckigen Raster befinden. Wenn der Druckbereich eines Blattes beispielsweise zwei Seiten breit und zwei hoch ist, zählt dies als 4 Seiten. Was aber, wenn nur die erste und dritte Seite in diesem Raster druckbaren Inhalt haben? Mit anderen Worten, Seite (0,0) und Seite (1,1) werden gedruckt, Seite (0,1) und Seite (1,0) jedoch nicht. Dann sollte es 2 Seiten zählen, nicht 4. Jim K vor 7 Jahren 0
@JimK: Du hast recht, mein Ansatz erwartet, dass alle vier Seiten gedruckt werden, nicht nur die zwei mit druckfähigem Inhalt. Ich habe diesen Fall nicht getestet, aber ich nahm an, dass das gedruckte PDF alle vier Seiten enthalten sollte. Ich denke, es gibt keine Möglichkeit, einen "korrekten" ToC zu erstellen, wenn der Benutzer einige Seiten später aus dem Druck entfernt. Bei einem spärlich gefüllten 2x2-Seitenlayout kann der Benutzer an den "leeren Seiten" interessiert sein, um die vollständige Tabelle so zu erhalten, wie sie auf dem Bildschirm angeordnet ist. Da es jedoch zu viele mögliche Anwendungsfälle gibt, denke ich, dass sie nicht von einem Makro allein abgedeckt werden können. tohuwawohu vor 7 Jahren 0
Übrigens: Ich würde mich freuen, die mit dem Makro-Recorder aufgezeichneten seltsamen Dispatcher / Uno-Anrufe entfernen und durch ihre StarBasic-Entsprechungen ersetzen zu können. Dies würde die Lesbarkeit des Codes verbessern ... tohuwawohu vor 7 Jahren 0