My wife embarked on creating a textbook for Major British Writers at Abilene Christian University last summer. In the process she ran into a problem after duplicating iBooks Author (IBA) files to break up the book. When her students attempted to download and install the books, they found they could only have one of them on their iPads at a time. I’m writing this post to document what causes this and how I fixed it.
A Short History of the Problem.
If you think about it this was a prime textbook to move to digital. Every student has had to buy the Norton Anthology, a massive 4 inch thick tome that contains works written hundreds of years ago and now in the public domain. So gathering these works into a digital format should almost eliminate the cost of the book, and save the student having to carry around this 10 lb book everywhere.
As she was working on it, after gathering many of the works, she felt the content was too long for just one iBook. She decided to break it into four books, broken down by the era of the works ie. Renaissance, Neoclassical, Old English, Middle English.
You would think this would be a simple process, but it isn’t. You can’t cut and paste entire pages and sections from one iBooks Author document to another. Even cutting and pasting individual parts doesn’t move all the data and template information.
Her solution was to duplicate her original document and then delete the parts she didn’t want for each book. This gave her all the template layout she’d done, and all the content she added and formatted.
This seemed to work fine. She edited the now four books, published them directly from iBooks Author to her iPad with no problem. Then to distribute them to her class she exported .ibooks documents and put them on a server. The students connected directly from their iPads in Safari and downloaded them. She wasn’t distributing via iTunes yet because the books are still works in progress.
Then the problem showed up.
The students downloaded the first book fine, but when they attempted to add a second book, iBooks on their iPad told them that book was already installed on their iPad. It was seeing both books as the same, even though they had different titles and content.
She tried changing every setting she could find in iBooks Author but nothing mattered.
After her description I started having an idea what the problem was. It seemed to me Apple was using something internal to the file to uniquely identify a book. Similar to what they do with applications, and I guessed it was the same kind of identifier.
I expected the iBooks files to actually be bundles like applications and many other document types are. But it turns out they aren’t. I’m still not actually sure what file type they are, but BBEdit knows. I dropped the IBA file on BBEdit, expecting to wade through the binary format to find the expected identifier. BBEdit displayed the file as a collection of subfiles. Most were media files – jpegs, pdfs, etc – for the things included in the book. But down in the middle of the list of files was one called “index.xml”.
Viewing index.xml, I found what I was looking for. A GUID tag with the recognizable 32 character string of numbers and letters. All four of her IBA files had the same GUID.
I also opened the .ibooks file and there were a couple of places this GUID number appeared, but I decided they weren’t the place to do the edits, because when she makes changes and generates a new .ibooks file, they would get blown away.
Once I knew the problem the solution was pretty easy.
1. Generate a new GUID.
If you have the Xcode developer tools installed, open the terminal and type “uuidgen” and hit return. You’ll get a new UUID.
2. Open your .iba file in BBEdit and find the index.xml file.
Once you select this file from the list, you will get 2 lines of XML displayed on the right. Turn on “Soft Wrap Text” so you can more easily see the XML.
3. Look for the XML tag with GUID in it.
There should be a line that looks something like this:
This article will show you how to use a python script to generate a comment from the actual declaration of the method.
So you’d have something that looked like this (Click image at right for bigger)
XCode 4 Doesn’t Support Scripting of Any Kind
XCode 4 removed the Script Menu and all the hooks that used to be there. They don’t even let you use AppleEvents to set the selection. Read the How This Script Came To Be section of the post for more details.
What you have to do is create a Service that receives the selected text, pass it to your script as an argument, then takes your script’s output and replace the selected text with it.
I wrote a Service once a long time ago using XCode and Cocoa, but that’s really way more than we need to do. Instead we will us Automator to handle the Service part for us.
Create An Automator Service
1. Launch Automator.
I just hit Cmd-Space, and start typing till I see it. But it’s in the Application folder if you’d rather click to it.
2. Create A New Workflow.
Cmd-N, or File > New
3. Select “Service” from the Choose Type Panel
4. Find the Run Shell Script Action. If the Library isn’t showing, click the button in the tool bar, or View > Show Library.
Then there is a little search field at the top, type in Run and you should see the action (like the image at the left).
Drag the Run Shell Script action to the right hand panel.
5. Configure the Run Shell Script Action.
The core the commenting work is the script. I’m specifically using it to make method comments, but you could write your own script to do anything. Brian wrote scripts that aligned selected routine names so the colons lined up vertically, that’s pretty cool. I’ve written scripts to take a list of serial numbers and format them as variable declarations to a pirate list. The sky is the limit. Select /usr/bin/python form the Shell: popup. You could also write your script in a different language. If you do select a different shell from the popup.
Here’s the script I’m using for the commenting:
RoutineCommenter.py I’ll talk about the details in a minute, but right now downloaded it, open it in a text editor like TextWrangler or even XCode, copy its content, and paste it into the Run Shell Script text area.
6. Save your Automator Workflow
Cmd-S, or File > Save. Instead of a standard save dialog, you get a little panel that says “Save service as:” and lets you enter the name you want to use. Pick carefully because this is what will be in the Services menu. And I couldn’t figure out how to change it later. Ended up duplicating the file just to change the name.
The Automator file is actually created in ~/Library/Services.
That’s it as far and hooking up the script. You can try it out by opening some code in XCode4, selecting a declaration in the code, and going to Apple > Services > Generate Method Comment, or whatever you named it.
A new comment will appear and you can even Shift Tab and get taken to the description bubble for editing.
You also can do this in most text editors, so even is you dont’ use XCode you can use the service.
How the Script Works
I won’t go into the details, because I think I did a pretty good job documenting it. Basically it just parses through the string, finds the parts it needs, and generates strings for the comment. It doesn’t really try to understand the code, it just gets what it needs to generate what I want for the comment.
You can go in and change the print statements in main() to reformat it to your comment style.
Debugging An Automator Shell Script Service
Editing and debugging a script in Automator is a pain in the ass, to the point it really isn’t viable . For one you can’t run the Service in XCode and get any feedback in Automator. What Automator will suggest if you click the run button, is you add a “Get Specific Text” action before and it’s output will be passed to your script. This “works”. You run and fail in Automator.
But you can’t read python error messages in the Automator Log window. You only get the first line of the error, which is bad because python outputs a traceback first, so every error is multiple lines long.
Here’s what I ended up doing.
Really you need to run it from the command line, and you need a real text editor, not just a text view. So I opened a new document in TextWrangler (for some reason it seems wrong to use XCode4). I pasted the code from my Automator action into the TextWrangler document and saved it as a .py file.
You’ve got my .py file to start with. You’ll need to change the name of the file you downloaded. I had to add .txt to the end for security reasons.
To run your script you are going to use python from the command line in Terminal. Find where you saved your script and cd there in terminal. Type “python RoutineCommenter.py” and you’ll get a wonderful error message because there is no text passed in to parse. No arguments at all.
You could add a string of a method declaration to your command line, but one thing you’ll want to test is how your script handles multi-line declarations.
Luckily I’ve given you a way in the code. Look at line 26-29:
TESTING = False
# for debugging here are some string to parse.
testString = “- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation”
And then at 102-103:
if TESTING: routineName = testString
else: routineName = sys.argv
If you set TESTING to True, then instead of using a passed in argument from the command line, the code uses testString, which is defined on line 29. Make Testing True and go back and rerun your script. Now you will see a comment.
There are a few other bits of debugging code I left in, but commented out. Hopefully they make sense if you need them.
Some things you might want to watch out for when debugging your own scripts here.
Multi-line inputs. If the declaration has returns in it, you can run into problems. For me I take out all of the returns and get a flat declaration before processing.
Tabs and Spaces. There is a difference and they can trick you. There are places in the code where I search for whitespace/code breaks by looking for a space. Really I probably should look for a tab character too.
Formatting Output. Tabs and spaces are a pain on output too. Luckily we’re programmers here and are probably using a monospace font for our code. So I just used spaces for tabs. Matter of fact I wrote a little routine that generates a number of spaces. Then I made a convenience method that returns a tab. Then I can just put it in a print statement and have a “tab” of spaces. There is also a constant for the number of spaces in a tab, so you can easily change it is you want more or less.
How This Script Came To Be
I was away from coding for months and then got a gig converting a board game to the iPhone/iPad. With much excitement I updated my tools to XCode 4 and jumped into it. There are a lot of cool features and I was overall impressed. Then I wrote my first code and realized I hadn’t migrated my scripts that generate method headers by selecting the method declaration in the code.
One of my former co-workers, Brian Webster of Fat Cat Software, had written a cool script to do it, and we put them in XCode 3’s script folder and were off to the races.
You know how it is with things like installing script menu items or editing templates etc that you did once along time ago. I couldn’t figure out where the scripts went. Then I starting looking around online and realized quickly the Script Menu was GONE in XCode4. Matter of fact there was NO way to even get the selection and pass it to AppleScript and then set it back.
I looked around occasionally for a couple of months and never found a way to do it. But today I got a little fed up. Code Snippets just weren’t doing it for me, so I went looking again. General consensus was Apple had screwed its developers again and there was no way to do it.
Then I found Brad Oliver’s post on using Automator and Perl to do it. That was all I needed, I could figure out how to replace the selection now. I just needed to find those scripts again. And I couldn’t. So I emailed Brian and started building the Service to do the replacing. When I had that figured out, I hadn’t heard back from Brian, so I just started writing my own script to do the processing.
I wrote the scripts in Python which I had never used before. I’d avoided Python because the idea of using indentation as bracketing had just rubbed me wrong. But I’d been looking into doing some game programming and found there are a lot of 3D engines that are scripted via Python, so I’d decided to learn it. This seemed as good a time as any. I was pleasantly surprised. I made a lot of progress in a relatively short amount of time, especially considering I knew nothing of the language this morning.
Hopefully this post will help you both scripting XCode4 and commenting your code better. If you have any questions feel free to ask them in comments.
UPDATE: 07/17/2012 made a change to the script to handle routines with no parameters.
I’m back from my week working the MacWorld San Fransisco show. My feet hurt. The show buzz can be summed up in 1 word.
I want one yesterday, but alas can’t have one till June. It looks like a sweet little computer with a phone built in. I’ll have to switch back to Cingular from T-Moble, which isn’t great, but should be easy enough.
I also read Tinker by Wen Spenser and it was a good engrossing book. I bought it the first night I was there and only read it at night and finished it in 3 days. Great high tech meets elves book. I’d like the sequal, but I got a number of books for my birthday and will read them first.
Gun people watch for comments on Tactical Pistol Marksmanship by Gabriel Suarez. I didn’t know this till I got the book, but he seems to be associated with FrontSight in some way. They pictures on the back are FrontSight instructors. The Mrs and I are booked for FrontSight the first weekend in Feburary. Woot!
I can never tell what will make my wife jealous. I am very blessed to have a wife who doesn’t care that I take pictures of pretty girls, often dressed in very little. We talk about stuff I figure other couples would be shocked about. But then she’d get jealous at the strangest stuff.
I’ve been trying to think of a name for my new computer. I’ve had it for weeks and it is still named “Ron Davis’ Computer”. How lame is that?
I tend to name my machines after women. My car is named Shelia. I wanted a similar name for my computer. I also knew it was going to have a Goth feel to it because I’m immersed in hot goth chicks right now.
On our trip this weekend I was listening to a podcast and came up with the name Violet. The podcast was open source sex hosted by Violet Blue. I turned to the Mrs and said “How about Violet for my computer’s name?”
And she gave me the look.
Apparently she didn’t like Violet. “Are you listening to Violet Blue?”
I wasn’t afraid to tell her. She’d listened to Violet’s podcast before and liked it. So I said “Yes”.
“You can’t name your computer after her.”
We ended up discussing it over a couple of days. Where I contended it wasn’t really named after Violet Blue, but rather inspired by her. When I think of Violet, I think of a little girl in a field of flowers. Dressed in black, with pale skin and blond hair. Not much like VB.
But to her it would be “another women contending for my attention”. I think she meant she was more jealous of time spent on the computer than Violet Blue.
I had put forth the name Victoria, but also rejected it because I felt it too hard to type.
Tonight I reconsidered and she approved because she doesn’t know any Victorias. I told her I was naming it after Joel Olsteen’s wife, just to make it a real person.
Then I said she’d have a secret middle name. Which of course was guessed to be Violet. But she approved of it this time.
And it feels very goth. Would make a great goth model name.
[This message typed on Victoria Violet]
(This is also probably the only place on the internet where Violet Blue and Joel Olsteen are both linked to from the same post.)
FedEx made me very happy yesterday. My new computer is in Houston, about 2 miles from where I’m sitting at work. But it isn’t due for delivery until tomorrow.
Called them last night and asked if I could just pick it up today. The guy said to call back that they sometimes deliver it early and I needed to check on that today. But if they weren’t planning on it, I could come pick it up today.
So I’m making a place for it in my office last night, staying up late because I know I’m going have my new computer today.
But when I call this morning I’m told its in a container and I can’t pick it up till they’ve tried to deliver it once. Which won’t happen until tomorrow.
Now life sucks. 🙁
Don’t get people’s hopes up if you aren’t going to deliver. I was singing the praises of FedEx last night for their flexibility. Now I’m blogging how they suck.