Created
December 21, 2023 21:13
-
-
Save MikulasZelinka/e10132407ed2d8766b4621274aafec1b to your computer and use it in GitHub Desktop.
Sort a Google Doc by Headings (sections) while preserving contents (paragraphs) of each section
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function onOpen() { | |
var ui = DocumentApp.getUi(); | |
// Create custom menu | |
ui.createMenu('Sort and Write') | |
.addItem('Sort and Write Ascending', 'sortAndWriteAscending') | |
.addItem('Sort and Write Descending', 'sortAndWriteDescending') | |
.addToUi(); | |
} | |
function sortAndWriteAscending() { | |
sortAndWrite(true); | |
} | |
function sortAndWriteDescending() { | |
sortAndWrite(false); | |
} | |
function sortAndWrite(ascending=true) { | |
var heading2List = extractHeadingStructure(); | |
var entries = convertToEntryStructure(heading2List); | |
var sortedEntries = sortEntries(entries, ascending); | |
writeEntriesToDoc(sortedEntries) | |
} | |
// Function to extract Heading structure and paragraphs into a list of objects | |
function extractHeadingStructure() { | |
var doc = DocumentApp.getActiveDocument(); | |
var body = doc.getBody(); | |
// Create a list to store Heading 2 objects | |
var heading2List = []; | |
var paragraphs = body.getParagraphs(); | |
var currentHeading2 = null; | |
var currentHeading2Object = null; | |
var currentHeading3 = null; | |
function parseHeading2Date(text) { | |
// Extract the year (YYYY) from Heading 2 text | |
var match = text.match(/\b\d{4}\b/); | |
var result = match ? parseInt(match[0]) : null | |
if (result) { | |
return result; | |
} | |
Logger.log("ERROR parsing year: " + text + " into date"); | |
} | |
function parseHeading3Date(text) { | |
// Extract the sortable date (DD.MM.) from Heading 3 text | |
var match = text.match(/\b(\d{1,2})[^\d]*(\d{1,2})\b/); | |
if (match) { | |
var day = match[1].padStart(2, '0'); | |
var month = match[2].padStart(2, '0'); | |
return day + '.' + month + '.'; | |
} | |
Logger.log("ERROR parsing day.month: " + text + " into date"); | |
return null; | |
} | |
paragraphs.forEach(function (paragraph) { | |
var textStyle = paragraph.getAttributes(); | |
var style = textStyle[DocumentApp.Attribute.HEADING]; | |
// Skip empty headings | |
if (style && ((style === DocumentApp.ParagraphHeading.HEADING2) || (style === DocumentApp.ParagraphHeading.HEADING3)) && paragraph.getText().trim() === '') { | |
return; | |
} | |
if (style === DocumentApp.ParagraphHeading.HEADING2) { | |
// Handle Heading 2 | |
currentHeading2 = paragraph.getText(); | |
currentHeading3 = null; | |
currentHeading2Object = { heading2: currentHeading2, heading3List: [], date: parseHeading2Date(currentHeading2) }; | |
heading2List.push(currentHeading2Object); | |
} else if (style === DocumentApp.ParagraphHeading.HEADING3) { | |
// Handle Heading 3 | |
currentHeading3 = { heading3: paragraph.getText(), paragraphs: [], date: parseHeading3Date(paragraph.getText()) }; | |
if (currentHeading2Object) { | |
currentHeading2Object.heading3List.push(currentHeading3); | |
} | |
} else { | |
// Handle regular paragraph text | |
if (currentHeading3) { | |
// If there is a Heading 3, append to its list of paragraphs | |
currentHeading3.paragraphs.push(paragraph.copy()); | |
} | |
} | |
}); | |
Logger.log('Heading 2 List:'); | |
Logger.log(JSON.stringify(heading2List, null, 2)); | |
// Return the extracted list of objects | |
return heading2List; | |
} | |
// Function to convert Heading 2, Heading 3 structure to Entry structure | |
function convertToEntryStructure(heading2List) { | |
var entryList = []; | |
heading2List.forEach(function (heading2Obj) { | |
var heading2 = heading2Obj.heading2; | |
var heading3List = heading2Obj.heading3List; | |
heading3List.forEach(function (heading3Obj) { | |
var heading3 = heading3Obj.heading3; | |
var date = heading3Obj.date; | |
var paragraphs = heading3Obj.paragraphs; | |
// Extract day and month from the date | |
var match = date.match(/(\d{1,2})\.(\d{1,2})\./); | |
var day = match ? match[1] : ''; | |
var month = match ? match[2] : ''; | |
// Create an Entry object | |
var entry = { | |
year: heading2, | |
month: month, | |
day: day, | |
title: "", | |
text: paragraphs | |
}; | |
entryList.push(entry); | |
}); | |
}); | |
Logger.log('Entry List:'); | |
Logger.log(JSON.stringify(entryList, null, 2)); | |
// Return the converted list of Entry objects | |
return entryList; | |
} | |
// Function to sort entries based on date in ascending or descending order | |
function sortEntries(entryList, ascending) { | |
// Convert year, month, and day to integers for comparison | |
entryList.forEach(function (entry) { | |
entry.year = parseInt(entry.year); | |
entry.month = parseInt(entry.month); | |
entry.day = parseInt(entry.day); | |
}); | |
// Default to ascending order if the sort direction is not provided or invalid | |
var sortDirection = ascending ? 1 : -1; | |
// Custom sorting function based on year, month, and day | |
function customSort(a, b) { | |
// Compare by year first, then month, and finally day | |
if (a.year !== b.year) { | |
return sortDirection * (a.year - b.year); | |
} else if (a.month !== b.month) { | |
return sortDirection * (a.month - b.month); | |
} else { | |
return sortDirection * (a.day - b.day); | |
} | |
} | |
// Sort the entryList using the custom sort function | |
entryList.sort(customSort); | |
// Log the sorted entryList | |
Logger.log('Sorted Entry List:'); | |
Logger.log(JSON.stringify(entryList, null, 2)); | |
// Return the sorted entryList | |
return entryList; | |
} | |
// Function to create a new Google Doc and write entries in the specified order | |
function writeEntriesToDoc(sortedEntryList) { | |
var doc = DocumentApp.getActiveDocument(); | |
var currentDocName = doc.getName(); | |
var newDoc = DocumentApp.create(currentDocName + '_sorted'); | |
var newDocBody = newDoc.getBody(); | |
// Czech month names | |
var czechMonths = [ | |
"Leden", "Únor", "Březen", "Duben", "Květen", "Červen", "Červenec", | |
"Srpen", "Září", "Říjen", "Listopad", "Prosinec" | |
]; | |
var czechMonthsGenitiv = [ | |
"ledna", "února", "března", "dubna", "května", "června", "července", | |
"srpna", "září", "října", "listopadu", "prosince" | |
]; | |
// Initialize variables to keep track of current year, month, and day | |
var currentYear = null; | |
var currentMonth = null; | |
var currentDay = null; | |
// Iterate through the sorted entries and write to the new document | |
sortedEntryList.forEach(function (entry) { | |
// Check if the year has changed | |
if (entry.year !== currentYear) { | |
newDocBody.appendParagraph(String(entry.year)).setHeading(DocumentApp.ParagraphHeading.HEADING2); | |
currentYear = entry.year; | |
// Reset month and day when year changes | |
currentMonth = null; | |
currentDay = null; | |
} | |
// Check if the month has changed | |
if (entry.month !== currentMonth) { | |
newDocBody.appendParagraph(`${czechMonths[entry.month - 1]} ${entry.year}`).setHeading(DocumentApp.ParagraphHeading.HEADING3); | |
currentMonth = entry.month; | |
// Reset day when month changes | |
currentDay = null; | |
} | |
// Check if the day has changed | |
if (entry.day !== currentDay) { | |
// var formattedDate = Utilities.formatString('%02d. %02d. %d', entry.day, entry.month, entry.year); | |
var formattedDate = `${entry.day}. ${czechMonthsGenitiv[entry.month - 1]} ${entry.year}`; | |
newDocBody.appendParagraph(formattedDate).setHeading(DocumentApp.ParagraphHeading.HEADING4); | |
currentDay = entry.day; | |
} | |
// Append paragraphs as regular text, images as image, etc. | |
entry.text.forEach(function (paragraph) { | |
try { | |
newDocBody.appendParagraph(paragraph); | |
} catch { | |
try { | |
newDocBody.appendListItem(paragraph); | |
} catch { | |
try { | |
newDocBody.appendImage(paragraph); | |
} catch { | |
try { | |
newDocBody.appendPageBreak(paragraph); | |
} catch { | |
try { | |
newDocBody.appendTable(paragraph); | |
} catch (e) { | |
throw e; | |
} | |
} | |
} | |
} | |
} | |
}); | |
}); | |
// Log the URL of the new document | |
Logger.log('New document created and entries written successfully.'); | |
Logger.log('New document URL: ' + newDoc.getUrl()); | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment