OO-PG0020
Plug-In: Import Copied Spreadsheet
A specialized command for importing tab-return-delimited spreadsheet data from the clipboard. TIP: For best results, start with a new blank empty outline.
Columns and rows are added as required, with the option for the outline column (topic) to be used for row headers.
A form is presented prior to input enabling the user to indicate column formatting (numeric) and to indicate whether the copied data contains row and/or column headers.
|
Return to: OmniOutliner Plug-In Collection
Column Title Alignment
/*{
"type": "action",
"targets": ["omnioutliner"],
"author": "Otto Automator",
"identifier": "com.omni-automation.oo.import-copied-spreadsheet",
"version": "1.1",
"description": "A specialized command for importing tab-return-delimited spreadsheet data from the clipboard.",
"label": "Import Copied Spreadsheet",
"shortLabel": "Import Spreadsheet",
"paletteLabel": "Import Spreadsheet",
"image": "square.and.arrow.down.fill"
}*/
(() => {
const action = new PlugIn.Action(async function(selection, sender){
try {
function createUtterance(textToSpeak){
langCode = Speech.Voice.currentLanguageCode
voiceObj = Speech.Voice.withLanguage(langCode)
utterance = new Speech.Utterance(textToSpeak)
utterance.voice = voiceObj
utterance.rate = Speech.Utterance.defaultSpeechRate
return utterance
}
var synthesizer = new Speech.Synthesizer()
try {document.name} catch(err){
throw {name:"Missing Resource",message:"No outline document is open."}
}
// CHECK CLIPBOARD CONTENT
var clipText = Pasteboard.general.string
if(!clipText.includes("\t")){
throw {
name: "Missing Data",
message: "There is no tab-delimited spreadsheet data on the clipboard."
}
}
// CREATE FORM
inputForm = new Form()
formats = ["Text (abc)", "Number (123)", "Decimal (123.00)", "Percent (123%)", "Decimal Percent (123.12%)", "Decimal Separators (1,234.12)", "Currency"]
formatMenu = new Form.Field.Option(
"formatType",
"Format",
[0, 1, 2, 3, 4, 5, 6],
formats,
1
)
columnHeadersCheckbox = new Form.Field.Checkbox(
"firstRowIsColHeaders",
"1st data row is column headers",
true
)
rowHeadersCheckbox = new Form.Field.Checkbox(
"firstColumnIsRowHeaders",
"1st data column is row headers",
true
)
inputForm.addField(formatMenu)
inputForm.addField(columnHeadersCheckbox)
inputForm.addField(rowHeadersCheckbox)
// PRESENT FORM WITH SPOKEN PROMPT
formPrompt = "Format for added columns:"
formButtom = "Continue"
spokenPrompt = "Please indicate the format and parameters for the added columns."
utterance = createUtterance(spokenPrompt)
synthesizer.speakUtterance(utterance)
formObject = await inputForm.show(formPrompt, formButtom)
synthesizer.stopSpeaking(Speech.Boundary.Word)
// RETRIEVE FORM RESULTS
var firstRowIsColHeaders = formObject.values["firstRowIsColHeaders"]
var firstColumnIsRowHeaders = formObject.values["firstColumnIsRowHeaders"]
var formatType = formObject.values['formatType']
switch(formatType) {
case 0:
var columnType = Column.Type.Text
var fmtr = null
break;
case 1:
var columnType = Column.Type.Number
var fmtr = Formatter.Decimal.plain
break;
case 2:
var columnType = Column.Type.Number
var fmtr = Formatter.Decimal.decimal
break;
case 3:
var columnType = Column.Type.Number
var fmtr = Formatter.Decimal.percent
break;
case 4:
var columnType = Column.Type.Number
var fmtr = Formatter.Decimal.percentWithDecimal
break;
case 5:
var columnType = Column.Type.Number
var fmtr = Formatter.Decimal.thousandsAndDecimal
break;
case 6:
var columnType = Column.Type.Number
var fmtr = Formatter.Decimal.currency()
break;
default:
var columnType = Column.Type.Number
var fmtr = Formatter.Decimal.plain
break;
}
// CLEAN CLIPBOARD TEXT TO ARRAY OF ROW DATA
dataRows = clipText.split("\n")
var tableData = []
dataRows.forEach(dataRow => {
tableData.push(dataRow.split("\t"))
})
// ADD COLUMNS AS REQUIRED
var tree = document.outline
var editor = document.editors[0]
editor.autosizeTopicColumn = true
if(firstRowIsColHeaders){
columnTitles = tableData[0]
tableData.shift()
}
var numRows = tableData.length
var numCols = tableData[0].length
var columnRefs = []
for (i = 0; i < numCols; i++) {
if(firstColumnIsRowHeaders && firstRowIsColHeaders && i === 0){
columnRefs.push(outlineColumn)
outlineColumn.title = columnTitles[0]
} else {
newCol = tree.addColumn(
columnType,
editor.afterColumn(null),
column => {
if(firstRowIsColHeaders){
column.title = columnTitles[i]
}
if(fmtr){column.formatter = fmtr}
}
)
columnRefs.push(newCol)
}
}
// REMOVE GLOBAL COLUMN TITLE ALIGNMENTS
columnTitleStyle.set(Style.Attribute.ParagraphAlignment, null)
// POPULATE THE OUTLINE WITH DATA
tableData.forEach(dataRow => {
tree.rootItem.addChild(null, item => {
columnRefs.forEach((col, index) => {
dataItem = dataRow[index]
if(firstColumnIsRowHeaders && index === 0){
item.setValueForColumn(dataItem, outlineColumn)
} else {
if(formatType !== 0){
// REMOVE NON-NUMERIC CHARACTERS ($, €, £, etc.) EXCEPT "."
dataItem = dataItem.replace(/[^\d\.]/g,"")
dataItem = parseFloat(dataItem)
}
item.setValueForColumn(dataItem, col)
}
})
})
})
utterance = createUtterance("Done!")
synthesizer.speakUtterance(utterance)
}
catch(err){
if(!causedByUserCancelling){
utterance = createUtterance(err.message)
synthesizer.speakUtterance(utterance)
new Alert(err.name, err.message).show()
}
}
});
action.validate = function(selection, sender){
return Pasteboard.general.hasStrings
};
return action;
})();