18

To export google spreadsheet's single worksheet to CSV, integer worksheet index(GID) is required to be passed.

https://spreadsheets.google.com/feeds/download/spreadsheets/Export?key=%s&gid=%d&exportFormat=csv

But, where are those informations? With gdata.spreadsheets.client, I could find some string id for worksheet like "oc6, ocv, odf".

client = gdata.spreadsheets.client.SpreadsheetsClient()
feed = client.GetWorksheets(spreadsheet, auth_token=auth_token)

And it returns below atom XML. (part of it)

<entry gd:etag="&quot;URJFCB1NQSt7ImBoXhU.&quot;">
    <id>https://spreadsheets.google.com/feeds/worksheets/0AvhN_YU3r5e9dGpTWGx3UVU3MTczaXJuNEFKQjMwN2c/ocw</id>
    <updated>2012-06-21T08:19:46.587Z</updated>
    <app:edited xmlns:app="http://www.w3.org/2007/app">2012-06-21T08:19:46.587Z</app:edited>
    <category scheme="http://schemas.google.com/spreadsheets/2006" term="http://schemas.google.com/spreadsheets/2006#worksheet"/>
    <title>AchievementType</title>
    <content type="application/atom+xml;type=feed" src="https://spreadsheets.google.com/feeds/list/0AvhN_YU3r5e9dGpTWGx3UVU3MTczaXJuNEFKQjMwN2c/ocw/private/full"/>
    <link rel="http://schemas.google.com/spreadsheets/2006#cellsfeed" type="application/atom+xml" href="https://spreadsheets.google.com/feeds/cells/0AvhN_YU3r5e9dGpTWGx3UVU3MTczaXJuNEFKQjMwN2c/ocw/private/full"/>
    <link rel="http://schemas.google.com/visualization/2008#visualizationApi" type="application/atom+xml" href="https://spreadsheets.google.com/tq?key=0AvhN_YU3r5e9dGpTWGx3UVU3MTczaXJuNEFKQjMwN2c&amp;sheet=ocw"/>
    <link rel="self" type="application/atom+xml" href="https://spreadsheets.google.com/feeds/worksheets/0AvhN_YU3r5e9dGpTWGx3UVU3MTczaXJuNEFKQjMwN2c/private/full/ocw"/>
    <link rel="edit" type="application/atom+xml" href="https://spreadsheets.google.com/feeds/worksheets/0AvhN_YU3r5e9dGpTWGx3UVU3MTczaXJuNEFKQjMwN2c/private/full/ocw"/>
    <gs:rowCount>280</gs:rowCount>
    <gs:colCount>28</gs:colCount>
</entry>

Also I tried with sheet parameter but failed with "Invalid Sheet" error.

https://spreadsheets.google.com/feeds/download/spreadsheets/Export?key=%s&sheet=XXX&exportFormat=csv

I guess there should be some magic function but could not find it. How can I convert them to integer id?? Or Can I export worksheet with string id?

EDIT: I just made convert table with python. DIRTY but working :-(

GID_TABLE = {
    'od6': 0, 
    'od7': 1, 
    'od4': 2, 
    'od5': 3, 
    'oda': 4, 
    'odb': 5, 
    'od8': 6, 
    'od9': 7, 
    'ocy': 8, 
    'ocz': 9, 
    'ocw': 10, 
    'ocx': 11, 
    'od2': 12, 
    'od3': 13, 
    'od0': 14, 
    'od1': 15, 
    'ocq': 16, 
    'ocr': 17, 
    'oco': 18, 
    'ocp': 19, 
    'ocu': 20, 
    'ocv': 21, 
    'ocs': 22, 
    'oct': 23, 
    'oci': 24, 
    'ocj': 25, 
    'ocg': 26, 
    'och': 27, 
    'ocm': 28, 
    'ocn': 29, 
    'ock': 30, 
    'ocl': 31, 
    'oe2': 32, 
    'oe3': 33, 
    'oe0': 34, 
    'oe1': 35, 
    'oe6': 36, 
    'oe7': 37, 
    'oe4': 38, 
    'oe5': 39, 
    'odu': 40, 
    'odv': 41, 
    'ods': 42, 
    'odt': 43, 
    'ody': 44, 
    'odz': 45, 
    'odw': 46, 
    'odx': 47, 
    'odm': 48, 
    'odn': 49, 
    'odk': 50, 
    'odl': 51, 
    'odq': 52, 
    'odr': 53, 
    'odo': 54, 
    'odp': 55, 
    'ode': 56, 
    'odf': 57, 
    'odc': 58, 
    'odd': 59, 
    'odi': 60, 
    'odj': 61, 
    'odg': 62, 
    'odh': 63, 
    'obe': 64, 
    'obf': 65, 
    'obc': 66, 
    'obd': 67, 
    'obi': 68, 
    'obj': 69, 
    'obg': 70, 
    'obh': 71, 
    'ob6': 72, 
    'ob7': 73, 
    'ob4': 74, 
    'ob5': 75, 
    'oba': 76, 
    'obb': 77, 
    'ob8': 78, 
    'ob9': 79, 
    'oay': 80, 
    'oaz': 81, 
    'oaw': 82, 
    'oax': 83, 
    'ob2': 84, 
    'ob3': 85, 
    'ob0': 86, 
    'ob1': 87, 
    'oaq': 88, 
    'oar': 89, 
    'oao': 90, 
    'oap': 91, 
    'oau': 92, 
    'oav': 93, 
    'oas': 94, 
    'oat': 95, 
    'oca': 96, 
    'ocb': 97, 
    'oc8': 98, 
    'oc9': 99
}
Jakub Wasilewski
  • 2,916
  • 22
  • 27
Ray Yun
  • 1,571
  • 3
  • 16
  • 22
  • 1
    I'm trying to do more or less the same thing. In my case, I'm programmatically generating spreadsheets and I need to store the links to individual worksheets in a separate database. – Tom Jul 11 '12 at 17:03
  • 6
    Alright, Google. This is a joke, right? Very funny. Now can we please have proper worksheet indices instead of this crazy stuff?... – Roman Starkov Nov 08 '12 at 23:29
  • If someone is facing the same question, then here is simple answer: https://code.google.com/p/gdata-python-client/issues/detail?id=698#c6 – Rozkalns Feb 25 '15 at 23:27

6 Answers6

28

I found your question looking for a solution to the same problem, and was surprised that those worksheet IDs actually correspond 1:1 to gids - I originally assumed they were assigned independently, instead of being an exercise in obfuscation.

I was able to find a slightly cleaner solution by reverse-engineering the formula they use to generate worksheet IDs from your table:

worksheetID = (gid xor 31578) encoded in base 36

So, some Python to go from a worksheet ID to gid:

def to_gid(worksheet_id):
    return int(worksheet_id, 36) ^ 31578

This is still dirty, but will work for GIDs higher than 99 without requiring giant tables. At least as long as they don't change the generation logic (which they probably won't, as it would break existing IDs that people already use).

Jakub Wasilewski
  • 2,916
  • 22
  • 27
  • 2
    Can you guide us through the process of figuring it out ? – letronje Jul 25 '13 at 13:29
  • 4
    I noticed repeating patterns. The first four values end in 6,7,4,5 the next four: a(10),b(11),8,9 - the same progression. I noticed it would work like this if you XORed consecutive numbers with something ending in `10` in binary. That's how I figured those strings were actually numbers. They contained all of the letters, so I figured it must be base-36. Finding the XOR factor was simple from there. – Jakub Wasilewski Nov 24 '13 at 00:20
  • Anyone have a conversion for .NET? – maguy Jan 17 '14 at 04:27
  • 5
    This is the insanest, randomest undocumented yet fundamentally orderly critically-useful behavior that I've ever heard of. My hats are off to you Jakub for figuring that out. And for being so nonchalant about the whole thing. – David M. Perlman May 01 '14 at 06:05
9

This code works with the new Google Sheets.

// Conversion of Worksheet Ids to GIDs and vice versa
// od4 > 2
function wid_to_gid(wid) {
    var widval = wid.length > 3 ? wid.substring(1) : wid;
    var xorval = wid.length > 3 ? 474 : 31578;
    return parseInt(String(widval), 36) ^ xorval;
}
// 2 > od4
function gid_to_wid(gid) {
    var xorval = gid > 31578 ? 474 : 31578;
    var letter = gid > 31578 ? 'o' : '';
    return letter + parseInt((gid ^ xorval)).toString(36);
}
Buho
  • 91
  • 1
  • 1
5

I cannot add a comment to Wasilewski's post because apparently I lack reputation so here are the two conversion functions in Javascript based on Wasilewski's answer:

// Conversion of Worksheet Ids to GIDs and vice versa
// od4 > 2
function wid_to_gid(wid) {
    return parseInt(String(wid),36)^31578
}
// 2> 0d4
function gid_to_wid(gid) {
    // (gid xor 31578) encoded in base 36
    return parseInt((gid^31578)).toString(36);
}
Mario
  • 2,619
  • 1
  • 24
  • 22
2

This is a Java adaptation of Buho's code which works with both the new Google Sheets and with the legacy Google Spreadsheets.

// "od4" to 2  (legacy style)
// "ogtw0h0" to 1017661118 (new style)
public static int widToGid(String worksheetId) {

    boolean idIsNewStyle = worksheetId.length() > 3;

    // if the id is in the new style, first strip the first character before converting
    worksheetId = idIsNewStyle ? worksheetId.substring(1) : worksheetId;

    // determine the integer to use for bitwise XOR
    int xorValue = idIsNewStyle ? 474 : 31578;

    // convert to gid
    return Integer.parseInt(worksheetId, 36) ^ xorValue;

}

// Convert 2 to "od4" (legacy style)
// Convert 1017661118 to "ogtw0h0"  (new style)
public static String gidToWid(int gid) {

    boolean idIsNewStyle = gid > 31578;

    // determine the integer to use for bitwise XOR
    int xorValue = idIsNewStyle ? 474 : 31578;

    // convert to worksheet id, prepending 'o' if it is the new style.
    return 
        idIsNewStyle ? 
        'o' + Integer.toString((worksheetIndex ^ xorValue), 36): 
        Integer.toString((worksheetIndex ^ xorValue), 36);

}
Julie
  • 31
  • 1
  • 4
0

This is a Clojure adaptation of Buho's and Julie's code which should work with both the new Google Sheets and with the legacy Google Spreadsheets.

(defn wid->gid [wid]
  (let [new-wid? (> (.length wid) 3)
        wid      (if new-wid? (.substring wid 1) wid)
        xor-val  (if new-wid? 474 31578)]
    (bit-xor (Integer/parseInt wid 36) xor-val)))

(defn gid->wid [gid]
  (let [new-gid? (> gid 31578)
        xor-val  (if new-gid? 474 31578)
        letter   (if new-gid? "o" "")]
    (str letter (Integer/toString (bit-xor gid xor-val) 36))))
leontalbot
  • 2,513
  • 1
  • 23
  • 32
0

If you're using Python with gspread, here's what you do:

wid = worksheet.id
widval = wid[1:] if len(wid) > 3 else wid
xorval = 474 if len(wid) > 3 else 31578
gid = int(str(widval), 36) ^ xorval

I'll probably open a PR for this.

laike9m
  • 18,344
  • 20
  • 107
  • 140