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="email@example.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:
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:
The API has some nice filtering options. You can also upload as well.