5

I wrote a database web app for handling church worship songs, and I'm trying to add a module to output selected songs for projecting the lyrics. I initially thought all my users were using Powerpoint, which can import a simple text file with each line preceded by 0-5 tabs (0-tab line becomes the title of a new slide, and one or more tabs means a "bullet point" with the level corresponding to how many tabs). So my module currently outputs such a text file, and the presenter would open a Powerpoint template styled the way they want for song presentation, insert the text file as an "outline" for new slides, and voila. Here is a small sample of the text file structure that works in Powerpoint (with one slide in Japanese with romanization in smaller font by utilizing the next level of outline):

As the Deer
    As the deer panteth for the water
    So my soul longeth after Thee
    You alone are my heart's desire
    And I long to worship Thee
As the Deer
    You alone are my strength, my shield
    To You alone may my spirit yield
    You alone are my heart's desire
    And I long to worship Thee
鹿のように (As the Deer)
    谷川の流れを したう鹿のように
        tanigawa no nagare o shitau shika no yoo ni
    主よ、わが魂 あなたをしたう
        shu yo, waga tamashii anata o shitau
鹿のように (As the Deer)
    あなたこそ わが盾、 あなたこそ わが力
        anata koso waga tate, anata koso waga chikara
    あなたこそ わが望み われは主をあおぐ
        anata koso waga nozomi, ware wa shu o aogu

But my church's pastor (whose computer is used for the projection) and the two other worship leaders besides me who select songs all use Macs. They always talked about slides for Powerpoint, so I assumed that's what they were using. But just as I finished the code to output tabbed text, I found out that when they say "Powerpoint" they really mean Keynote, which has no ability to use plain text files. Old versions of Keynote internally stored slide data in XML (https://www.xml.com/pub/a/2004/01/07/keynote.html), but newer Keynote versions use progressively more opaque formats (http://justsolve.archiveteam.org/wiki/IWork). Apple clearly doesn't want anything except Keynote creating or editing Keynote presentations.

So I'm looking for suggestions on how to approach this. I'd like a process that doesn't involve me creating a Powerpoint file for them to convert to Keynote. Not only should I not be in the loop every week, but there are some line spacing problems in the conversion.

I know how to use LaTeX to generate PDFs from the data (I currently only do it for printed chordsheets, but I'm sure I could figure out how to do slide-style layouts), but Keynote apparently only imports one page of a PDF at a time - a typical Sunday worship set would be around 30-50 slides, so that would be pretty annoying. Plus, I would like the worship leader/pastor to be able to adjust things in Keynote if necessary - if it comes from a PDF, that won't be possible. So my first choice is to somehow marry a Keynote "template" with text of some sort (XML, JSON, tabbed text, or whatever).

This conversation seemed to hint that it might be possible with Applescript (which sounds like the iWorks equivalent of Office's VBA), but since I don't own a Mac, that would require long hours of borrowing someone else's computer to learn the language and develop/test the script (unless it's simple enough that one of you is willing to whip up something for me). Ideas?


Edit: After CJK's answer and comments, I realized that an example of an end result might help visualize what I'm trying to do. Here are two slides from Powerpoint, choosing Japanese slide examples, because if I can get this to work, English slides are easy. When I tried to import this Powerpoint into Keynote a week ago, the most stubborn styling was the line spacing of the Japanese (level one "bullet") and romanization (level two).

(In case you're wondering, yes, that's the title box at the bottom - I know it's unconventional, but due to a low ceiling, the top half of our slides are prime real estate.)

enter image description here

On this next example, the non-seasonal version, I added something Powerpoint's text file import doesn't support, because CJK's approach inspired me to see how it could be done with rich text in the script: Two different text formats within the title box. (Previously I had planned to just put the copyright info on the last slide of each song, below the lyrics using level 3 or 4 of the "bullet" styles. But I like it better with the title.) enter image description here

Edit 2: Attempting to springboard from CJK's script, here is an attempt at code to use an existing master slide like "Title & Bullets" and paragraph styles (completely untested - just bits from examples online). I'll be able to test it in two days, but I'm putting it here so CJK can see what I've done so far:

-- *** I'd like to use relative path so it would be portable, but (path to home folder as text) gave errors ***
property SambiDBTextFile : "/Users/Rachel/Desktop/Songs.txt"

property masterSlideName : "Lyrics" -- custom master slide based on "Title & Bullets"

-- ** If I can use paragraph styles, I won't need these ***
property TextSizes : {32, 28, 20}
property TextColours : {"white", {63222,57568,41634}, {63222,57568,41634}}
property TextFonts : {"Hiragino Kaku Gothic Pro", "Arial Italic", "Hiragino Kaku Gothic Pro"}

-- ** This is what I really want to use, but I don't know if I can ***
property TextStyles : {"Main Lyrics", "Romaji Lyrics", "Song Credits"}

set AppleScript's text item delimiters to tab
set notes to paragraphs of (read SambiDBTextFile)

tell application "Keynote" to tell current document

    -- *** Check for master slide existence, and substitute if absent ***
    set masterSlideList to the name of every master slide
    if masterSlideName is not in masterSlideList then
        -- *** Create master slide? Nah, probably not possible ***
        display alert ("Master Slide") message "Master slide '" & masterSlideName & "' not found; using 'Title & Bullets' instead."
        set masterSlideName to "Title & Bullets"
    end if

    -- Create slides with content from Keynote text file
    repeat with i from 1 to number of notes
        if item i of notes is "" then exit repeat -- EOF

        -- Get the text (without tabstops) and the level of indentation
        set [TextContent, TabValue] to [last text item, number of rest of reverse of text items] of item i of notes

        if TabValue is 0 then -- Indicates title of new slide
            set current slide to make new slide with properties {base slide:master slide masterSlideName}
            set object text of the default title item to TextContent
        else -- TabValue is not 0, indicating lyrics
            if TabValue > 3 then set TabValue to 3

            -- *** I have no idea if this will work, but the point is to append
            set object text of default body item to object text of default body item & TextContent & return

            -- *** Style the line just added ***
            -- *** Plan A: use paragraph styles (not sure if I can do this) ***
            set paragraph style of paragraph ((count of paragraphs of default body item) - 1) of default body item to item TabValue of TextStyles

            -- *** Plan B: hardcoded styling (uncomment if above line doesn't work) ***
            --tell paragraph ((count of paragraphs of default body item) - 1) of default body item
                --set its color to item TabValue of TextColours
                --set its font to item TabValue of TextFonts
                --set its size to item TabValue of TextSizes
            --end tell
        end if
    end repeat
end tell
OsakaWebbie
  • 645
  • 1
  • 7
  • 21

1 Answers1

4

Having played around a little bit with Keynote and AppleScript today, I think the following script will produce something along the lines of what you want. It includes the option to set different text settings based on the level of tabulation (1-5) of each line within the text file being parsed.

    property KeynoteTextFile : "/Users/CK/Desktop/Keynote.txt"

    property PresentationTitle : "My Presentation"
    property _W : 1024 -- The width of each slide
    property _H : 768 -- The Height of each slide

    -- Text properties for the cover title and each slide title
    property CoverTextStyle : {font:"Arial Bold", color:"white", size:96}
    property TitleTextStyle : {font:"Arial Bold", color:"white", size:48}

    -- Spacing above and below the title of each slide
    property TitleMargins : {top:30, bottom:100}
    -- Spacing between lines in the body of each slide
    property VerticalSpacing : 75

    -- Text properties for the body of each slide for
    -- each level of tabulation
    property Tabulations : {0.1, 0.2, 0.3, 0.4, 0.5}
    property TextSizes : {32, 28, 24, 20, 16}
    property TextColours : {"white", "blue", "green", "magenta", "orange"}
    property TextFonts : {"Arial", "Arial Italic", "Times New Roman Bold", ¬
        "Times New Roman Bold Italic", "Times New Roman Italic"}


    set AppleScript's text item delimiters to tab
    set notes to paragraphs of (read KeynoteTextFile)

    -- Create new presentation with cover slide
    tell application "Keynote" to tell (make new document with properties ¬
        {document theme:theme "Black", width:_W, height:_H})

        set MyPresentation to it

        set base slide of current slide to master slide "Blank"

        tell the first slide to ¬
            set CoverTitle to make new text item ¬
                with properties {object text:PresentationTitle}

        set properties of object text of the CoverTitle to CoverTextStyle
    end tell

    -- Create slides with content from Keynote text file
    repeat with i from 1 to number of notes
        if item i of notes is "" then exit repeat -- EOF

        -- Get the text (without tabstops)
        -- and the level of indentation
        set [TextContent, TabValue] to ¬
            [last text item, number of rest of reverse of text items] ¬
                of item i of notes

        if TabValue is 0 then -- Indicates title of new slide
            tell application "Keynote"

                tell (make new slide at end of slides of MyPresentation) to ¬
                    set Title to make new text item ¬
                        with properties {object text:TextContent}

                set properties of object text of the Title to TitleTextStyle
                copy position of Title to [_x, _y]
                set position of Title to [_x, |top| of TitleMargins]

            end tell
        else -- TabValue is not 0, indicating slide content
            if TabValue > 5 then set TabValue to 5

            tell application "Keynote" to tell current slide of MyPresentation
                set n to number of text items

                set T to make new text item with properties ¬
                    {object text:TextContent}

                tell object text of T
                    set its color to item TabValue of TextColours
                    set its font to item TabValue of TextFonts
                    set its size to item TabValue of TextSizes
                end tell

                set position of T to ¬
                    [(item TabValue of Tabulations) * _W, ¬
                        VerticalSpacing * n + (|bottom| of TitleMargins)]

            end tell
        end if
    end repeat

    -- Go to first slide of presentation and bring Keynote
    -- into the foreground
    tell application "Keynote"
        set current slide of MyPresentation to first slide of MyPresentation
        activate
    end tell

Here are some lines from my Keynote.txt file:

This is a Title
    This is indented by 1 tab
    So is this
        This is indented by 2 tabs
            This is 3 tabs
    Back to 1 tab

which produced this slide:

A Keynote slide made using AppleScript

Bear in mind that the level of indenting on the actual slide is determined, not by the tabs in the text file, but by the values of the property tabulations defined at the top of the script. Hence, the only effect the presence of a tab in the parsed text file will have will be to determine the set of characteristics to apply to the text when it's rendered on the slide (font, colour, size, and indent independent of its text file indent).

As a final note, I should point out that the lines in the text file can begin with 0-5 tabs. 6 or more tabs are treated as though there were just 5 tabs. However, importantly, the rest of the line should not contain any tabs within the text. As the script stands, this will produce some odd results. It's possible to adjust the script to cater for lines that need to contain tabs within the text, but I didn't see the need at present.

CJK
  • 5,732
  • 1
  • 8
  • 26
  • Thanks! I'll try it when I get access to a Mac in a couple days, but meanwhile, I have one question: Assuming that Keynote has multiple levels of bullets like Powerpoint, do you know how to specify the level in AppleScript when adding a line? The lines with two tabs are intended to be at the second level of bullet (so that a template can define the styling to be different from level 1), not the first level with a literal tab. If you don't know, that's okay - I thought I'd ask in case you do. I tried to google it, but all the examples were too simple to even mention levels. – OsakaWebbie Dec 29 '17 at 14:38
  • 1
    @OsakaWebbie Sorry, I knew you wanted the degree of tab indentation to set the level of bullet, but I don't know how to do that. This answer actually represents my entire *Keynote* experience to date. What I did notice whilst going through the AppleScript dictionary for *Keynote* is that the property `object text` takes rich text formatted-text, and itself has properties such as `font`, `color`, and `size` that you would be able to set like this: `set size of object text of text item 2 to 12`. You may find [this](https://iworkautomation.com/keynote/text-item-behavior.html) page useful. – CJK Dec 29 '17 at 14:59
  • 1
    @OsakaWebbie I've revised the answer completely having had time to play around with Keynote and AppleScript. The new script lets you set each individual property for the text based on its level of indentation, plus a few other general properties. Note that, although in my sample slide, I did choose to indent *"This is 3 tabs"* by 3 tabs, there's no need to do this: you can simply change the third item of property `tabulations` from `0.3` to `0.1`, say, to have it line up with the left-hand margin. Let me know your thoughts. – CJK Dec 29 '17 at 17:51
  • Wow, that's a huge help! (I'll mark as answer once I've tried it.) I had seen that page about text items when looking for levels, but was still thinking of formatting with master elements in a pre-made file (because that's how it would be done in PPT). But perhaps in the Apple culture it's more common to directly style the text... Anyway, is what you called the "AppleScript dictionary for *Keynote*" that iworkautomation site (always the first Google hit), or something on Macs themselves? – OsakaWebbie Dec 30 '17 at 03:46
  • I edited the question to include a couple example slides, in case you were curious how I envisioned the end result. – OsakaWebbie Dec 30 '17 at 05:24
  • @OsakaWebbie The Keynote dictionary is a dictionary that comes with the *Keynote* application and that you access using *Script Editor*. It details all the scripting terminology, syntax, etc. It’s available on the Mac itself. It looks like you’ll be able to achieve your outcome using my script above, just with some tweaks regarding placement of the title and body text. If you need more help with that, let me know. – CJK Dec 31 '17 at 08:53
  • I agree, and I tried to do exactly that today, but naturally I can't use your hardcoded path, so I changed the first line to `property KeynoteTextFile : (path to home folder) & "Desktop/Songs.txt"`. But when I tried to compile (I think that's what I was supposed to do - there is almost nothing on the web about how to actually **use** a script), `read KeynoteTextFile` threw: **error** "Can't make {alias \"Macintosh HD:Users:Rachel:\", \"Desktop/Songs.txt\"} into type file." number -1700 from {*alias* \"Macintosh HD:Users:Rachel:\", \"Desktop/Songs.txt\"} to *file* – OsakaWebbie Dec 31 '17 at 15:04
  • Don’t use `path to home folder`. Just use *"/Users/Rachel/Desktop/Songs.txt"* – CJK Jan 01 '18 at 02:57
  • @OsakaWebbie How did you get on with this ? – CJK Jan 04 '18 at 22:58
  • I'm still working on it (and have an edit to my question half-written), but the main point is that after realizing the definition of `text item` (not just a string but what I would call a text box) I see that your script makes each line a separate box with absolute positioning, which won't allow for a long line to wrap or the user to make changes to the lyrics. The lines need to be "paragraphs" in a single text item to allow text flow. I'm trying to work on the script myself, but lack of easy access to the syntax "dictionary" and a test Mac make it slow. – OsakaWebbie Jan 05 '18 at 02:52
  • I'm also trying to find out if it's possible to use **paragraph styles** rather than hardcoding the size, color, etc. (It wasn't in 2009, but I haven't found a more recent discussion.) E.g. the user might want to change colors to match a seasonal BG - if the color is set manually for each string he'd have to edit every string (or ask me to make a custom script), but if it's a style, he only has to edit the style. Ideally it would even use a theme (master slide) with pre-existing text items, rather than "Blank", so that positioning can also be changed globally without coding. – OsakaWebbie Jan 05 '18 at 03:12
  • @OsakaWebbie Well, my script is meant to be a template that can be adjusted and tweaked to suit your precise needs, although I appreciate that you’re not familiar with AppleScript to necessarily make the needed adjustments. Let me take a look over your comments and my script and see what I can do to getting you closer to your objective. – CJK Jan 05 '18 at 07:21
  • 1
    Thanks. I don't expect you to do my work for me - that's why I was silent for a few days, hoping to figure out more of it myself. I'm an experienced programmer in other languages, but AppleScript syntax is unique, and there's no decent online documentation - the closest thing is the https://iworkautomation.com/keynote/ site, but it's nowhere near exhaustive (either that or AppleScript is really wimpy). And the site won't let me copy code snippets - I have to retype everything. I might be making progress now, but I won't know for sure until Sunday, my weekly chance to touch a Mac. – OsakaWebbie Jan 05 '18 at 12:39
  • See Edit #2 on my question - am I on the right track? – OsakaWebbie Jan 05 '18 at 14:30
  • @OsakaWebbie I'll have a look at your edits in more detail over the weekend. However, happy to report that text objects inside text items can have their text styled on a per paragraph, per word, or even per character basis, e.g. `set color of first paragraph of object text of T to "blue"` I'll implement this change before Sunday, hopefully, and update my script accordingly. We might be better off moving this to-and-fro out of the comments thread and into a chat where we can work on it together. – CJK Jan 05 '18 at 18:09
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/162636/discussion-between-osakawebbie-and-cjk). – OsakaWebbie Jan 06 '18 at 00:05