Quick example of the Google Docs API

This post is more than 2 years old.

Earlier this morning a user forwarded me a job request from someone looking for code to integrate with Google Docs. In the past I've really detested Google's APIs, but I figured I'd take a look and see how bad it is. Turns out it wasn't as bad as I thought. I wrote up a quick demo. This is not some new project. This was just written for fun and as a proof of concept.

The first thing you have to before calling the Google Docs API (any Google service I believe) is to authenticate. Now if you remember my complaints about the Calendar API - they had some funky login process that took 3 steps, including one that "may" occur. I love API's with "may" in them. The authentication API now is somewhat more simpler. Consider:

<cfhttp url="https://www.google.com/accounts/ClientLogin" method="post" result="result" charset="utf-8"> <cfhttpparam type="formfield" name="accountType" value="HOSTED_OR_GOOGLE"> <cfhttpparam type="formfield" name="Email" value="rcamden@gmail.com"> <cfhttpparam type="formfield" name="Passwd" value="whereiscfonadobedotcom"> <cfhttpparam type="formfield" name="service" value="writely"> <cfhttpparam type="formfield" name="source" value="camden-cfgoogledocs-0"> </cfhttp>

This is a fairly typical form post. Some things to note: The accoutnType is supposed to be either Google or Hosted, but they also allow for HOSTED_OR_GOOGLE if you don't know which one you are using. Kudos to Google for doing something helpful. The second thing to note is that the names for Email and Passwd must match the case I've shown above. Why? I don't know. Case-sensitivity is the Lindsey Lohan of Comp Sci. The service is, obviously, the service you are connecting too. Writely is the service value for docs. The source value is simply a name you give for your automated program. I shoulda called my super-megatron-9000 or something, but camden-cfgoogledocs-0 worked as well.

If done correctly, this returns a string of values in this form:

LSID=Longfreakinglist
SID=Evenmorecrap
Auth=Stuff

I wrote up some quick code to treat the result like a list and store the results:

<cfset content = result.filecontent> <cfset authdata = structNew()>

<cfloop index="line" list="#content#" delimiters="#chr(10)#"> <cfset dtype = listFirst(line, "=")> <cfset value = listRest(line, "=")> <cfset authdata[dtype] = value> </cfloop>

Now you have authentication information. This can be passed in with your quest:

<cfhttp url="http://docs.google.com/feeds/documents/private/full" method="get" result="result" charset="utf-8"> <cfhttpparam type="header" name="Authorization" value="GoogleLogin auth=#authdata.auth#"> </cfhttp>

I've asked for a generic list of documents and I've passed the authorization header. This returns an Atom feed. Now as we know, CFFEED can parse this nicely. But CFFEED requires a URL or a real file. So I just treated it like a normal XML packet:

<cfset packet = xmlParse(result.filecontent)> <cfloop index="x" from="1" to ="#arrayLen(packet.feed.entry)#"> <cfset entry = packet.feed.entry[x]> <cfset title = entry.title.xmltext> <cfset updated = entry.updated.xmltext> <cfset type = entry.category.xmlattributes.label> <cfset sourceurl = entry.content.xmlattributes.src> <cfoutput>[#type#] #title# (#updated#)<br></cfoutput> </cfloop>

What I've done here is loop over the entry results. I grabbed some of the values - not all - and I output them. The type variable will signify if the document is a written doc, spreadsheet, or presentation.

Oddly - there is no way to directly download or export a document. You do get access to the "content" URL in the result. Notice I store the above. If you want, you can get the contents like so:

<cfhttp url="#somesourceurl#" method="get" result="result" charset="utf-8"> <cfhttpparam type="header" name="Authorization" value="GoogleLogin auth=#authdata.auth#"> </cfhttp> <cfoutput>#result.filecontent#</cfoutput>

This returns a textual version of the document. When I say text I mean HTML as well. The result I got mirrored nicely in HTML what I had seen when writing the document.

Not too bad. Someone could wrap this up into a CFC in a few minutes. I didn't cover the entire API here - see this doc for more information:

http://code.google.com/apis/documents/overview.html

The API has some nice filtering options. You can also upload as well.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Raymond Camden posted on 12/8/2007 at 1:45 AM

I lie. I got a CFC done. I'll post later. It allows for this though:

<cfset docs = createObject("component", "docs")>
<cfset docs.authenticate("rcamden@gmail.com","xxxxxx")>
<cfset mydocs = docs.getDocumentList()>
<cfdump var="#mydocs#">

<cfset content = docs.download(mydocs.sourceurl[1])>
<cfoutput>result is #content#</cfoutput>

Comment 2 by Adam Tuttle posted on 12/8/2007 at 2:52 AM

Very cool, Ray! I can't wait for it to show up on RIAForge - I'm using google docs to share some documentation for an admin site with a buddy and this should let me just display the contents of the doc right on the site!

Comment 3 by Raymond Camden posted on 12/8/2007 at 2:54 AM

No. This will not be a project. Never. I can't. Not another project. Please, help me stop the madness!

Comment 4 by Max Hamby posted on 12/8/2007 at 3:16 AM

Geez, Ray.... not another project? What do you want to do instead.... sleep?

slacker! ;)

Comment 5 by Michael Evangelista posted on 12/9/2007 at 12:44 AM

LOL @
value="whereiscfonadobedotcom"

agreed... where?

Comment 6 by Raymond Camden posted on 12/9/2007 at 12:57 AM

That was my way of being a little snide. ;) As it stands - CF is now on the home page!

Comment 7 by Dave Griffith posted on 12/29/2007 at 11:21 AM

Raymond - Any idea why I would be getting a "Connection Failure" result while trying your example with valid values???? Every time I try the page spins out for about 45 seconds and the "result.filecontent" contains the failure message.

I'm on a Godaddy-hosted CF instance and cannot use CreateObject. I've pasted your example into an http page with no luck. I also changed your sample cfc to <cfinvokes... to get around the CreateObject limitation.

Happy New Year!

Comment 8 by Raymond Camden posted on 12/29/2007 at 8:01 PM

I'm not sure what you mean by 'pasting into an http page' - but I'd consider one simple CFM with a CFHTTP that mimics my code. If it still fails, you need to talk to GoDaddy.

Comment 9 by Andrew posted on 6/25/2008 at 11:22 PM

Just a quick note that I found this great ... had to adapt it to MX6.1 for a project but not too bad. Instead of arrayLen(packet.feed.entry) I used packet.feed.totalResults.xmlText in production because it allows for a 0 entry not to throw a CF error since not all my users have Google Docs. This may have been added by Google after you wrote this so I wanted to give this heads up to everyone finding their way here.

Comment 10 by omar posted on 3/19/2011 at 9:57 PM

I'm guessing this doesn't work anymore. I just tested the code as is.

At code block 3 I get:

Element AUTH is undefined in AUTHDATA.

Comment 11 by Raymond Camden posted on 3/19/2011 at 10:24 PM

It could be something else. What _does_ exist in authdata?

Comment 12 by omar posted on 3/19/2011 at 10:29 PM

If I print out <cfoutput>#authdata[dtype]#</cfoutput>

I get:

https://www.google.com/acco...

Comment 13 by Raymond Camden posted on 3/19/2011 at 10:52 PM

You get that url? It has my email address in it. Did you try to access _my_ docs? You would need to use your own auth info to test this.

Comment 14 by omar posted on 3/19/2011 at 11:31 PM

That was to demonstrate. I used my own. What I think is happening is that it's not authenticating.

I'm simply getting Error Bad Authentication. After refreshing a few times, Google sends Captcha codes back.

Not sure why this is happening, email and password are correct on my end. I'm just trying to google authenticate for gmail

<cfset email = URLENCODEDFORMAT("*******@gmail.com")>
<cfset userpwd = URLENCODEDFORMAT("*********")>

<cfhttp url="https://www.google.com/acco..." method="post" result="result" charset="utf-8">
<cfhttpparam type="formfield" name="Email" value="#email#">
<cfhttpparam type="formfield" name="Passwd" value="#userpwd#">
<cfhttpparam type="formfield" name="accountType" value="HOSTED">
<cfhttpparam type="formfield" name="service" value="apps">
</cfhttp>

<cfset content = result.filecontent>
<cfset authdata = structNew()>

<cfloop index="line" list="#content#" delimiters="#chr(10)#">
<cfset dtype = listFirst(line, "=")>
<cfset value = listRest(line, "=")>
<cfset authdata[dtype] = value>
<cfoutput>#dtype# <p />#value# <p />#authdata[dtype]#</cfoutput>

</cfloop>

Comment 15 by Raymond Camden posted on 3/19/2011 at 11:46 PM

Ah well shoot. You got me there. The code is 4 years old so it's very possible their API has been updated to require oAuth for example in order to work.

Comment 16 by omar posted on 3/20/2011 at 12:14 AM

no prob. I figured that. It sucks not to be able to find coldfusion examples for much of the basics. I was searching for coldfusion demos on authenticating users with Google. I'll play around some more.

Comment 17 by Jose G Alfonso posted on 12/15/2012 at 11:41 PM

Hi Ray, I've been using the googleDocsService.cfc successfully (thanks for the great work) I do have a question I've been googling around for 2 hours and not finding answers:

When a google document gets displayed by the viewer, you see several options and icons on the top left of the viewer: paging zoom and print (PDF). Do you know if it is possible to give a viewer a param so the print function opens a new window as opposed to what by default does which is to open the print preview in the same window?

Please let me know

Thanks

Here's a sample URL https://docs.google.com/vie...

Comment 18 by Raymond Camden posted on 12/16/2012 at 8:10 AM

I'm not quite sure I get what you're asking. You want to change the behavior of the browser's Print menu?

Comment 19 by Stu posted on 1/9/2013 at 11:24 PM

Hi Raymond.
I have been using very similar code to list files on Google Drive but for some reason it does not list video, image or Lucidcharts...any idea why?

Does it have to do with the difference between
"https://docs.google.com/fee..."> AND
"https://docs.google.com/fee...">
I am using the URL ...feeds/documents/... but I can't seem to get the ...feeds/default/... URL to work.

Comment 20 by Raymond Camden posted on 1/10/2013 at 8:05 PM

From what I can tell, Drive seems to be an entirely different API - https://developers.google.c...

This means my code here is probably obsolete.