suppressPackageStartupMessages(library("dplyr"))
library("xml2")
library("stringr")
## Warning: package 'stringr' was built under R version 3.1.3
## I want to use unexported functions here
devtools::load_all(pkg = "~/research/googlesheets/")
## Loading googlesheets
#library("googlesheets")
We need better documentation of what we can get our hands on via the different feeds. Here we go.
entry
nodes are where it's atThe most useful info from the spreadsheet feed consists of the entry
nodes, one per sheet.
the_url <- "https://spreadsheets.google.com/feeds/spreadsheets/private/full"
req <- gsheets_GET(the_url)
ns <- xml_ns_rename(xml_ns(req$content), d1 = "feed")
entries <- req$content %>%
xml_find_all(".//feed:entry", ns) %>%
xml_path()
length(entries)
## [1] 36
req$content %>%
xml_find_first(entries[1])
## {xml_node}
## <entry>
## [1] <id>https://spreadsheets.google.com/feeds/spreadsheets/private/full/ ...
## [2] <updated>2015-05-19T23:00:13.476Z</updated>
## [3] <category scheme="http://schemas.google.com/spreadsheets/2006" term= ...
## [4] <title type="text">ari copy</title>
## [5] <content type="text">ari copy</content>
## [6] <link rel="http://schemas.google.com/spreadsheets/2006#worksheetsfee ...
## [7] <link rel="alternate" type="text/html" href="https://spreadsheets.go ...
## [8] <link rel="self" type="application/atom+xml" href="https://spreadshe ...
## [9] <author>\n <name>gspreadr</name>\n <email>gspreadr@gmail.com</emai ...
The entry
nodes have same structure for each sheet, which we explore via the first entry = sheet. What is all this stuff?
id
is a URL.updated
is date-time of last update (not clear exactly what that means)category
seems utterly useless to me.title/text
and content/text
both give the sheet's title. Below we confirm they are redundant.author/name
and author/email
are self-explanatory.Is the info in title/text
identical to that in content/text
?
title_stuff <-
data_frame(text = req$content %>%
xml_find_all(".//feed:entry//feed:title", ns) %>%
xml_text(),
content = req$content %>%
xml_find_all(".//feed:entry//feed:content", ns) %>%
xml_text())
title_stuff
## Source: local data frame [36 x 2]
##
## text content
## 1 ari copy ari copy
## 2 Ari's Anchor Text Scraper Ari's Anchor Text Scraper
## 3 EasyTweetSheet - Shared EasyTweetSheet - Shared
## 4 #rhizo15 #tw #rhizo15 #tw
## 5 gas_mileage gas_mileage
## 6 2014-05-10_seaRM-at-vanNH 2014-05-10_seaRM-at-vanNH
## 7 2014-05-10_seaRM-at-vanNH_MY-COPY 2014-05-10_seaRM-at-vanNH_MY-COPY
## 8 test-gs-permissions test-gs-permissions
## 9 #TalkPay Tweets #TalkPay Tweets
## 10 test-gs-old-sheet2 test-gs-old-sheet2
## .. ... ...
with(title_stuff, identical(text, content))
## [1] TRUE
YES. At least for this set of sheets.
Let's set the names for entries
to the sheet titles.
names(entries) <- title_stuff$text
Each entry
node has an id
element containing a URL plus 3 additional nodes named link
. I gather all 4 into a tbl_df
for systematic exploration
jfun <- function(x) { # gymnastics required for one sheet's worth of links
x <- req$content %>% xml_find_first(x)
links <- x %>%
xml_find_all("feed:link", ns) %>%
lapply(xml_attrs) %>%
lapply(as.list) %>%
lapply(as_data_frame) %>%
bind_rows() %>%
mutate(source = "content/entry/link")
links %>%
rbind(data.frame(rel = NA, type = NA,
href = x %>% xml_find_first("feed:id", ns) %>% xml_text(),
source = "content/entry/id")) %>%
mutate(feed = "ss",
sheet_title = x %>% xml_find_first("feed:title", ns) %>% xml_text()) %>%
select(sheet_title, feed, source, href, rel, type)
}
links <- entries %>% lapply(jfun) %>%
bind_rows()
id
links the same?links %>%
filter(rel == "self" | source == "content/entry/id") %>%
group_by(sheet_title) %>%
summarize(query = n_distinct(href) == 1) %>%
`[[`("query") %>%
all
## [1] TRUE
YES, they are exactly the same, at least for these sheets.
links %>%
filter(rel == "self") %>%
`[[`("href") %>%
str_split_fixed("//*", n = 7)
## [,1] [,2] [,3] [,4] [,5]
## [1,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [2,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [3,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [4,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [5,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [6,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [7,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [8,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [9,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [10,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [11,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [12,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [13,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [14,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [15,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [16,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [17,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [18,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [19,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [20,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [21,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [22,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [23,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [24,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [25,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [26,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [27,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [28,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [29,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [30,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [31,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [32,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [33,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [34,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [35,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [36,] "https:" "spreadsheets.google.com" "feeds" "spreadsheets" "private"
## [,6] [,7]
## [1,] "full" "twqk79m_IJlhdWPdyvjqkDw"
## [2,] "full" "tQKSYVRwBXssUfYEaMdt-aw"
## [3,] "full" "14mAbIi1UyZtJTDuIa7iMb80xYtXbxCr-TGlvFbPgi3E"
## [4,] "full" "1oBQNnsMY8Qkuui6BAE8TnC1GphJS22Rodm3oVzbbemM"
## [5,] "full" "1WH65aJjlmhOWYMFkhDuKPcRa5mloOtsTCKxrF7erHgI"
## [6,] "full" "1223dpf3vnjZUYUnCM8rBSig3JlGrAu1Qu6VmPvdEn4M"
## [7,] "full" "1zeV34mJWN4T_Sl-F2lptyG1RebRRGvCPdWazWaBCeAQ"
## [8,] "full" "1gq8qy4JXJaz8I-14XV2_tAXv2JMng86G3WpvNkSf444"
## [9,] "full" "1IK1an_x8buIveByENsAk6eww-mHIfZeYQxr7FBWPWz8"
## [10,] "full" "1cee9I3hNJ1lAij1LBqzuLU3_UQY6Dd5atO9HBchZb4k"
## [11,] "full" "1BMtx1V2pk2KG2HGANvvBOaZM4Jx1DUdRrFdEx-OJIGY"
## [12,] "full" "1UXr4-haIQsmJfyjkEhlkNt2PXduBkB97e15jez9ogRo"
## [13,] "full" "1upHM4Kg9Zr3dmzW2LW_rMG44NFJruQOIv_FQ-YvRFT8"
## [14,] "full" "1F0iNuYW4v_oG69s7c5NzdoMF_aXq1aOP-OAOJ4gK6Xc"
## [15,] "full" "1BzfL0kZUz1TsI5zxJF1WNF01IxvC67FbOJUiiGMZ_mQ"
## [16,] "full" "1r_R_Qxw7FLM-X_HMhyJ4TQZ3Dok_7b-fRcqQ2K9mDmM"
## [17,] "full" "1hff6AzFAZgFdb5-onYc1FZySxTP4hlrcsPSkR0dG3qk"
## [18,] "full" "1OvDq4_BtbR6nSnnHnjD5hVC3HQ-ulZPGbo0RDGbzM3Q"
## [19,] "full" "1SDA_Gu7vCHr1hBXtgD7xB77SHd4YKy4zLZDrwnao0pM"
## [20,] "full" "1vFtO8i3zH8pk4RoxkcQw5kFtCDeooYVimUaXGD8n1WY"
## [21,] "full" "1P3ho4-XZcYyVAUZyYAHsWl3t9EW9MasbHBQkkT9i-yc"
## [22,] "full" "1ET1NGcPpAOKoqBBfcL1t1D2wcQ-3rc1H5RcYxY-TbTE"
## [23,] "full" "1HT5B8SgkKqHdqHJmn5xiuaC04Ngb7dG9Tv94004vezA"
## [24,] "full" "1yet5ONlyclG5nn63nQ8XDdexzxbZm0zx1wutYKzuzao"
## [25,] "full" "1w8F3t9-LpMqiKBgZG1RImRyMKleI6OH0_YE-tHQl-j0"
## [26,] "full" "10RqUOHuieOtW5baYJXBdIBECjnVKTORlBmglgGd2l_c"
## [27,] "full" "1Hkh20-IEQzKaBTqwWrQEYqoCaqDoyLjbgX8x4keACgE"
## [28,] "full" "1_tZXIrjS5M-hIQkOWwjotzqyL8hMZpsDH9P1C8RH9L4"
## [29,] "full" "t2nkTcxxWosQbEpIhF2udYA"
## [30,] "full" "17nhc-Dih2Usxz2m1rX5a8h_W0NXANeCXfW2J8oAS1gA"
## [31,] "full" "1GLsDOyR8hDgkjC6fzaDCVVjYsN8tLvnySDayk3HfxxA"
## [32,] "full" "1Ti9J6t4CSr7ffyXYXoX0HesIwrf9n4bGNZKX1vROQ2o"
## [33,] "full" "1bd5wjZQI8XjPrVNUFbTLI-zhpS8qLJ1scPq1v4v3mWs"
## [34,] "full" "tyMzjZuK5v7dCKE-azr-kQA"
## [35,] "full" "1HpaE4o3QLflLa8uQ6BLh9grHVlpv5kB7BGAPgIWMQ-Y"
## [36,] "full" "reBYenfrJHIRd4voZfiSmuw"
Here's what I see:
https://spreadsheets.google.com/feeds/spreadsheets/private/full/KEY
I happen to know that the worksheets feed is the link with attribute "rel"
equal to http://schemas.google.com/spreadsheets/2006#worksheetsfeed
.
links %>%
filter(str_detect(rel, "2006#worksheetsfeed")) %>%
`[[`("href") %>%
str_split_fixed("//*", n = 7)
## [,1] [,2] [,3] [,4]
## [1,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [2,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [3,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [4,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [5,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [6,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [7,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [8,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [9,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [10,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [11,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [12,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [13,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [14,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [15,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [16,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [17,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [18,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [19,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [20,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [21,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [22,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [23,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [24,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [25,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [26,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [27,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [28,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [29,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [30,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [31,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [32,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [33,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [34,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [35,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [36,] "https:" "spreadsheets.google.com" "feeds" "worksheets"
## [,5] [,6] [,7]
## [1,] "twqk79m_IJlhdWPdyvjqkDw" "private" "full"
## [2,] "tQKSYVRwBXssUfYEaMdt-aw" "private" "values"
## [3,] "14mAbIi1UyZtJTDuIa7iMb80xYtXbxCr-TGlvFbPgi3E" "private" "values"
## [4,] "1oBQNnsMY8Qkuui6BAE8TnC1GphJS22Rodm3oVzbbemM" "private" "values"
## [5,] "1WH65aJjlmhOWYMFkhDuKPcRa5mloOtsTCKxrF7erHgI" "private" "values"
## [6,] "1223dpf3vnjZUYUnCM8rBSig3JlGrAu1Qu6VmPvdEn4M" "private" "full"
## [7,] "1zeV34mJWN4T_Sl-F2lptyG1RebRRGvCPdWazWaBCeAQ" "private" "values"
## [8,] "1gq8qy4JXJaz8I-14XV2_tAXv2JMng86G3WpvNkSf444" "private" "full"
## [9,] "1IK1an_x8buIveByENsAk6eww-mHIfZeYQxr7FBWPWz8" "private" "values"
## [10,] "1cee9I3hNJ1lAij1LBqzuLU3_UQY6Dd5atO9HBchZb4k" "private" "full"
## [11,] "1BMtx1V2pk2KG2HGANvvBOaZM4Jx1DUdRrFdEx-OJIGY" "private" "values"
## [12,] "1UXr4-haIQsmJfyjkEhlkNt2PXduBkB97e15jez9ogRo" "private" "full"
## [13,] "1upHM4Kg9Zr3dmzW2LW_rMG44NFJruQOIv_FQ-YvRFT8" "private" "full"
## [14,] "1F0iNuYW4v_oG69s7c5NzdoMF_aXq1aOP-OAOJ4gK6Xc" "private" "full"
## [15,] "1BzfL0kZUz1TsI5zxJF1WNF01IxvC67FbOJUiiGMZ_mQ" "private" "values"
## [16,] "1r_R_Qxw7FLM-X_HMhyJ4TQZ3Dok_7b-fRcqQ2K9mDmM" "private" "values"
## [17,] "1hff6AzFAZgFdb5-onYc1FZySxTP4hlrcsPSkR0dG3qk" "private" "full"
## [18,] "1OvDq4_BtbR6nSnnHnjD5hVC3HQ-ulZPGbo0RDGbzM3Q" "private" "values"
## [19,] "1SDA_Gu7vCHr1hBXtgD7xB77SHd4YKy4zLZDrwnao0pM" "private" "full"
## [20,] "1vFtO8i3zH8pk4RoxkcQw5kFtCDeooYVimUaXGD8n1WY" "private" "values"
## [21,] "1P3ho4-XZcYyVAUZyYAHsWl3t9EW9MasbHBQkkT9i-yc" "private" "full"
## [22,] "1ET1NGcPpAOKoqBBfcL1t1D2wcQ-3rc1H5RcYxY-TbTE" "private" "values"
## [23,] "1HT5B8SgkKqHdqHJmn5xiuaC04Ngb7dG9Tv94004vezA" "private" "full"
## [24,] "1yet5ONlyclG5nn63nQ8XDdexzxbZm0zx1wutYKzuzao" "private" "full"
## [25,] "1w8F3t9-LpMqiKBgZG1RImRyMKleI6OH0_YE-tHQl-j0" "private" "full"
## [26,] "10RqUOHuieOtW5baYJXBdIBECjnVKTORlBmglgGd2l_c" "private" "values"
## [27,] "1Hkh20-IEQzKaBTqwWrQEYqoCaqDoyLjbgX8x4keACgE" "private" "full"
## [28,] "1_tZXIrjS5M-hIQkOWwjotzqyL8hMZpsDH9P1C8RH9L4" "private" "full"
## [29,] "t2nkTcxxWosQbEpIhF2udYA" "private" "values"
## [30,] "17nhc-Dih2Usxz2m1rX5a8h_W0NXANeCXfW2J8oAS1gA" "private" "full"
## [31,] "1GLsDOyR8hDgkjC6fzaDCVVjYsN8tLvnySDayk3HfxxA" "private" "full"
## [32,] "1Ti9J6t4CSr7ffyXYXoX0HesIwrf9n4bGNZKX1vROQ2o" "private" "full"
## [33,] "1bd5wjZQI8XjPrVNUFbTLI-zhpS8qLJ1scPq1v4v3mWs" "private" "full"
## [34,] "tyMzjZuK5v7dCKE-azr-kQA" "private" "values"
## [35,] "1HpaE4o3QLflLa8uQ6BLh9grHVlpv5kB7BGAPgIWMQ-Y" "private" "values"
## [36,] "reBYenfrJHIRd4voZfiSmuw" "private" "values"
Here's what I see:
https://spreadsheets.google.com/feeds/worksheets/KEY/VISIBILITY/FOO
where VISIBILITY
always equals private
when URL comes from the spreadsheets feed and FOO
is values
when user has only read permission and full
when user
is also allowed to write.
Note: I arranged the rows here for clarity.
links %>%
filter(rel == "alternate") %>%
arrange(href) %>%
`[[`("href") %>%
str_split_fixed("//*", n = 6)
## [,1] [,2]
## [1,] "https:" "docs.google.com"
## [2,] "https:" "docs.google.com"
## [3,] "https:" "docs.google.com"
## [4,] "https:" "docs.google.com"
## [5,] "https:" "docs.google.com"
## [6,] "https:" "docs.google.com"
## [7,] "https:" "docs.google.com"
## [8,] "https:" "docs.google.com"
## [9,] "https:" "docs.google.com"
## [10,] "https:" "docs.google.com"
## [11,] "https:" "docs.google.com"
## [12,] "https:" "docs.google.com"
## [13,] "https:" "docs.google.com"
## [14,] "https:" "docs.google.com"
## [15,] "https:" "docs.google.com"
## [16,] "https:" "docs.google.com"
## [17,] "https:" "docs.google.com"
## [18,] "https:" "docs.google.com"
## [19,] "https:" "docs.google.com"
## [20,] "https:" "docs.google.com"
## [21,] "https:" "docs.google.com"
## [22,] "https:" "docs.google.com"
## [23,] "https:" "docs.google.com"
## [24,] "https:" "docs.google.com"
## [25,] "https:" "docs.google.com"
## [26,] "https:" "docs.google.com"
## [27,] "https:" "docs.google.com"
## [28,] "https:" "docs.google.com"
## [29,] "https:" "docs.google.com"
## [30,] "https:" "docs.google.com"
## [31,] "https:" "docs.google.com"
## [32,] "https:" "spreadsheets.google.com"
## [33,] "https:" "spreadsheets.google.com"
## [34,] "https:" "spreadsheets.google.com"
## [35,] "https:" "spreadsheets.google.com"
## [36,] "https:" "spreadsheets.google.com"
## [,3] [,4]
## [1,] "spreadsheets" "d"
## [2,] "spreadsheets" "d"
## [3,] "spreadsheets" "d"
## [4,] "spreadsheets" "d"
## [5,] "spreadsheets" "d"
## [6,] "spreadsheets" "d"
## [7,] "spreadsheets" "d"
## [8,] "spreadsheets" "d"
## [9,] "spreadsheets" "d"
## [10,] "spreadsheets" "d"
## [11,] "spreadsheets" "d"
## [12,] "spreadsheets" "d"
## [13,] "spreadsheets" "d"
## [14,] "spreadsheets" "d"
## [15,] "spreadsheets" "d"
## [16,] "spreadsheets" "d"
## [17,] "spreadsheets" "d"
## [18,] "spreadsheets" "d"
## [19,] "spreadsheets" "d"
## [20,] "spreadsheets" "d"
## [21,] "spreadsheets" "d"
## [22,] "spreadsheets" "d"
## [23,] "spreadsheets" "d"
## [24,] "spreadsheets" "d"
## [25,] "spreadsheets" "d"
## [26,] "spreadsheets" "d"
## [27,] "spreadsheets" "d"
## [28,] "spreadsheets" "d"
## [29,] "spreadsheets" "d"
## [30,] "spreadsheets" "d"
## [31,] "spreadsheets" "d"
## [32,] "ccc?key=0Ak0qDiMLT3XddHlNempadUs1djdkQ0tFLWF6ci1rUUE" ""
## [33,] "ccc?key=0AonYZs4MzlZbcmVCWWVuZnJKSElSZDR2b1pmaVNtdXc" ""
## [34,] "ccc?key=0AphiLdjs9wK0dDJua1RjeHhXb3NRYkVwSWhGMnVkWUE" ""
## [35,] "ccc?key=0Audw-qi1jh3fdHdxazc5bV9JSmxoZFdQZHl2anFrRHc" ""
## [36,] "ccc?key=0Av8m6X4cYe9hdFFLU1lWUndCWHNzVWZZRWFNZHQtYXc" ""
## [,5] [,6]
## [1,] "10RqUOHuieOtW5baYJXBdIBECjnVKTORlBmglgGd2l_c" "edit"
## [2,] "1223dpf3vnjZUYUnCM8rBSig3JlGrAu1Qu6VmPvdEn4M" "edit"
## [3,] "14mAbIi1UyZtJTDuIa7iMb80xYtXbxCr-TGlvFbPgi3E" "edit"
## [4,] "17nhc-Dih2Usxz2m1rX5a8h_W0NXANeCXfW2J8oAS1gA" "edit"
## [5,] "1BMtx1V2pk2KG2HGANvvBOaZM4Jx1DUdRrFdEx-OJIGY" "edit"
## [6,] "1BzfL0kZUz1TsI5zxJF1WNF01IxvC67FbOJUiiGMZ_mQ" "edit"
## [7,] "1ET1NGcPpAOKoqBBfcL1t1D2wcQ-3rc1H5RcYxY-TbTE" "edit"
## [8,] "1F0iNuYW4v_oG69s7c5NzdoMF_aXq1aOP-OAOJ4gK6Xc" "edit"
## [9,] "1GLsDOyR8hDgkjC6fzaDCVVjYsN8tLvnySDayk3HfxxA" "edit"
## [10,] "1HT5B8SgkKqHdqHJmn5xiuaC04Ngb7dG9Tv94004vezA" "edit"
## [11,] "1Hkh20-IEQzKaBTqwWrQEYqoCaqDoyLjbgX8x4keACgE" "edit"
## [12,] "1HpaE4o3QLflLa8uQ6BLh9grHVlpv5kB7BGAPgIWMQ-Y" "edit"
## [13,] "1IK1an_x8buIveByENsAk6eww-mHIfZeYQxr7FBWPWz8" "edit"
## [14,] "1OvDq4_BtbR6nSnnHnjD5hVC3HQ-ulZPGbo0RDGbzM3Q" "edit"
## [15,] "1P3ho4-XZcYyVAUZyYAHsWl3t9EW9MasbHBQkkT9i-yc" "edit"
## [16,] "1SDA_Gu7vCHr1hBXtgD7xB77SHd4YKy4zLZDrwnao0pM" "edit"
## [17,] "1Ti9J6t4CSr7ffyXYXoX0HesIwrf9n4bGNZKX1vROQ2o" "edit"
## [18,] "1UXr4-haIQsmJfyjkEhlkNt2PXduBkB97e15jez9ogRo" "edit"
## [19,] "1WH65aJjlmhOWYMFkhDuKPcRa5mloOtsTCKxrF7erHgI" "edit"
## [20,] "1_tZXIrjS5M-hIQkOWwjotzqyL8hMZpsDH9P1C8RH9L4" "edit"
## [21,] "1bd5wjZQI8XjPrVNUFbTLI-zhpS8qLJ1scPq1v4v3mWs" "edit"
## [22,] "1cee9I3hNJ1lAij1LBqzuLU3_UQY6Dd5atO9HBchZb4k" "edit"
## [23,] "1gq8qy4JXJaz8I-14XV2_tAXv2JMng86G3WpvNkSf444" "edit"
## [24,] "1hff6AzFAZgFdb5-onYc1FZySxTP4hlrcsPSkR0dG3qk" "edit"
## [25,] "1oBQNnsMY8Qkuui6BAE8TnC1GphJS22Rodm3oVzbbemM" "edit"
## [26,] "1r_R_Qxw7FLM-X_HMhyJ4TQZ3Dok_7b-fRcqQ2K9mDmM" "edit"
## [27,] "1upHM4Kg9Zr3dmzW2LW_rMG44NFJruQOIv_FQ-YvRFT8" "edit"
## [28,] "1vFtO8i3zH8pk4RoxkcQw5kFtCDeooYVimUaXGD8n1WY" "edit"
## [29,] "1w8F3t9-LpMqiKBgZG1RImRyMKleI6OH0_YE-tHQl-j0" "edit"
## [30,] "1yet5ONlyclG5nn63nQ8XDdexzxbZm0zx1wutYKzuzao" "edit"
## [31,] "1zeV34mJWN4T_Sl-F2lptyG1RebRRGvCPdWazWaBCeAQ" "edit"
## [32,] "" ""
## [33,] "" ""
## [34,] "" ""
## [35,] "" ""
## [36,] "" ""
We've got more variety here, due to old sheets vs new. Note that, in addition to the obvious difference in domain and overall URL structure, the old sheets present an alternative key in the "alternate" link (verified explicitly below).
Here's what I see:
new Sheets:
https://docs.google.com/spreadsheets/d/KEY/edit
old Sheets:
"https://spreadsheets.google.com/ccc?key=ALT_KEY"
id = "self" link:
https://spreadsheets.google.com/feeds/spreadsheets/VISIBILITY/PROJECTION/KEY
VISIBILITY = {private (always in spreadsheets feed?), public}
PROJECTION = {full (default), basic}
worksheets feed:
https://spreadsheets.google.com/feeds/worksheets/KEY/VISIBILITY/PERMISSION
VISIBILITY = {private (always in spreadsheets feed?), public}
PERMISSION = {full (read and write), values (read only)}
"alternate" link, new sheets:
https://docs.google.com/spreadsheets/d/KEY/edit
"alternate" link, old sheets:
https://spreadsheets.google.com/ccc?key=ALT_KEY
Create new variables to hold info about whether a sheet is old or new and whether current user is allowed to read only or to read and write.
version_df <- links %>%
filter(rel == "alternate") %>%
transmute(sheet_title,
version = ifelse(grepl("^https://docs.google.com/spreadsheets/d",
href), "new", "old"))
perm_df <- links %>%
filter(str_detect(rel, "2006#worksheetsfeed")) %>%
transmute(sheet_title,
perm = ifelse(grepl("values", href), "r", "rw"))
links <- links %>%
left_join(version_df) %>%
left_join(perm_df)
## Joining by: "sheet_title"
## Joining by: "sheet_title"
Store the keys in these links as a variable.
links <- links %>%
mutate(link_key = extract_key_from_url(href))
Hypothesis: all link keys are uniform for a new sheet ("self" = id
agrees with worksheets feed agrees with "alternate").
links %>%
filter(version == "new") %>%
group_by(sheet_title) %>%
summarize(query = n_distinct(link_key) == 1) %>%
`[[`("query") %>%
all
## [1] TRUE
Hypothesis: The "self" and worksheets feed keys agree for an old sheet but differ from the "alternate" key.
links %>%
filter(rel %>% str_detect("2006#worksheetsfeed|self|alternate")) %>%
group_by(sheet_title) %>%
summarize(query = n_distinct(link_key), version = first(version)) %>%
group_by(version) %>%
summarize(min = min(query), max = max(query))
## Source: local data frame [2 x 3]
##
## version min max
## 1 new 1 1
## 2 old 2 2
This "alternate" key -- only defined for old sheets and only available through the "alternate" link found in the spreadsheets feed -- is ultimately useful to us for any operations that require the Drive API. Empirically, I note it can also be extracted from the URL seen in the browser when visiting such a sheet.
Formalize this notion of the (default) key versus the "alternate" key, which is only defined for old sheets and is damned hard to get.
alt_keys <- links %>%
filter(rel == "alternate") %>%
group_by(sheet_title) %>%
transmute(alt_key = ifelse(version == "new", NA_character_, link_key))
sheet_keys <- links %>%
filter(rel == "self") %>%
group_by(sheet_title) %>%
transmute(sheet_key = link_key)
links <- links %>%
left_join(alt_keys) %>%
left_join(sheet_keys)
## Joining by: "sheet_title"
## Joining by: "sheet_title"
Note the keys shown below are truncated! Wanted to fit more variables and show that alt_key
is NA
for new Sheets and that alt_key
!= sheet_key
for old Sheets.
links %>%
glimpse
## Observations: 144
## Variables:
## $ sheet_title (chr) "ari copy", "ari copy", "ari copy", "ari copy", "A...
## $ feed (chr) "ss", "ss", "ss", "ss", "ss", "ss", "ss", "ss", "s...
## $ source (chr) "content/entry/link", "content/entry/link", "conte...
## $ href (chr) "https://spreadsheets.google.com/feeds/worksheets/...
## $ rel (chr) "http://schemas.google.com/spreadsheets/2006#works...
## $ type (chr) "application/atom+xml", "text/html", "application/...
## $ version (chr) "old", "old", "old", "old", "old", "old", "old", "...
## $ perm (chr) "rw", "rw", "rw", "rw", "r", "r", "r", "r", "r", "...
## $ link_key (chr) "twqk79m_IJlhdWPdyvjqkDw", "0Audw-qi1jh3fdHdxazc5b...
## $ alt_key (chr) "0Audw-qi1jh3fdHdxazc5bV9JSmxoZFdQZHl2anFrRHc", "0...
## $ sheet_key (chr) "twqk79m_IJlhdWPdyvjqkDw", "twqk79m_IJlhdWPdyvjqkD...
links %>%
filter(source == "content/entry/id") %>%
#arrange(version, perm, sheet_title) %>%
mutate(sheet_title = substr(sheet_title, 1, 15),
sheet_key = substr(sheet_key, 1, 15),
alt_key = substr(alt_key, 1, 15)) %>%
select(sheet_title, perm, version, sheet_key, alt_key)
## Source: local data frame [36 x 5]
##
## sheet_title perm version sheet_key alt_key
## 1 ari copy rw old twqk79m_IJlhdWP 0Audw-qi1jh3fdH
## 2 Ari's Anchor Te r old tQKSYVRwBXssUfY 0Av8m6X4cYe9hdF
## 3 EasyTweetSheet r new 14mAbIi1UyZtJTD NA
## 4 #rhizo15 #tw r new 1oBQNnsMY8Qkuui NA
## 5 gas_mileage r new 1WH65aJjlmhOWYM NA
## 6 2014-05-10_seaR rw new 1223dpf3vnjZUYU NA
## 7 2014-05-10_seaR r new 1zeV34mJWN4T_Sl NA
## 8 test-gs-permiss rw new 1gq8qy4JXJaz8I- NA
## 9 #TalkPay Tweets r new 1IK1an_x8buIveB NA
## 10 test-gs-old-she rw new 1cee9I3hNJ1lAij NA
## .. ... ... ... ... ...
Now we turn to the worksheets feed. Hand-picked 4 example sheets: all possible combinations of new vs old sheets and sheets for which I do and do not have write permission.
example_sheets <- c("unitables2010final copy", "ari copy",
"WI15 ARCHY 499", "^Gapminder$")
examples <- example_sheets %>%
gs_ls() %>%
arrange(version, perm)
examples %>%
select(sheet_title, version, perm)
## Source: local data frame [4 x 3]
##
## sheet_title version perm
## 1 WI15 ARCHY 499 new r
## 2 Gapminder new rw
## 3 unitables2010final copy old r
## 4 ari copy old rw
Get the worksheets feed for each example sheet. Use sheet names to name the resulting list. Get overview of all the feeds and the first one as an example.
req_list <- examples$ws_feed %>%
lapply(gsheets_GET)
#names(req_list) <- substr(examples$sheet_title, 1, 12)
names(req_list) <- examples$sheet_title
req_list %>% str(max.level = 1)
## List of 4
## $ WI15 ARCHY 499 :List of 9
## ..- attr(*, "class")= chr "response"
## $ Gapminder :List of 9
## ..- attr(*, "class")= chr "response"
## $ unitables2010final copy:List of 9
## ..- attr(*, "class")= chr "response"
## $ ari copy :List of 9
## ..- attr(*, "class")= chr "response"
req_list[[1]] %>% str(max.level = 1)
## List of 9
## $ url : chr "https://spreadsheets.google.com/feeds/worksheets/1vFtO8i3zH8pk4RoxkcQw5kFtCDeooYVimUaXGD8n1WY/private/values"
## $ status_code: int 200
## $ headers :List of 13
## ..- attr(*, "class")= chr [1:2] "insensitive" "list"
## $ all_headers:List of 1
## $ cookies :List of 1
## $ content :List of 2
## ..- attr(*, "class")= chr [1:2] "xml_document" "xml_node"
## $ date : POSIXct[1:1], format: "2015-05-19 16:16:34"
## $ times : Named num [1:6] 0 0.000018 0.00002 0.000084 0.637948 ...
## ..- attr(*, "names")= chr [1:6] "redirect" "namelookup" "connect" "pretransfer" ...
## $ request :List of 5
## - attr(*, "class")= chr "response"
ns_ws <- xml_ns_rename(xml_ns(req_list[[1]]$content), d1 = "feed")
A worksheet feed request returns 9 components:
url
is the URL of the worksheets feed itself (this is true by definition; it's an httr
thing)status_code
, date
, times
are semi-self-explanatory and/or off-topicheaders
+ all_headers
, cookies
, request
call for some inspection (below)content
is, of course, where it's really at (next subsection)I have executed and inspected the below but it's not very interesting, nor is it related to our inventory of links. Remove chunk option eval = FALSE
if you want to bring it back.
req_list %>% lapply(`[[`, "cookies")
req_list %>% lapply(`[[`, "headers")
req_list %>% lapply(`[[`, "all_headers")
req_list %>% lapply(`[[`, "request")
It is convenient to create a named list holding just the content.
content <- req_list %>%
lapply(`[[`,"content")
content %>% str(max.level = 1)
## List of 4
## $ WI15 ARCHY 499 :List of 2
## ..- attr(*, "class")= chr [1:2] "xml_document" "xml_node"
## $ Gapminder :List of 2
## ..- attr(*, "class")= chr [1:2] "xml_document" "xml_node"
## $ unitables2010final copy:List of 2
## ..- attr(*, "class")= chr [1:2] "xml_document" "xml_node"
## $ ari copy :List of 2
## ..- attr(*, "class")= chr [1:2] "xml_document" "xml_node"
#xml2::xml_structure(content[[1]])
content[[1]]
## {xml_document}
## <feed>
## [1] <id>https://spreadsheets.google.com/feeds/worksheets/1vFtO8i3zH8pk4 ...
## [2] <updated>2015-04-13T20:14:47.253Z</updated>
## [3] <category scheme="http://schemas.google.com/spreadsheets/2006" term ...
## [4] <title type="text">WI15 ARCHY 499</title>
## [5] <link rel="alternate" type="application/atom+xml" href="https://doc ...
## [6] <link rel="http://schemas.google.com/g/2005#feed" type="application ...
## [7] <link rel="http://schemas.google.com/g/2005#post" type="application ...
## [8] <link rel="self" type="application/atom+xml" href="https://spreadsh ...
## [9] <author>\n <name>gayoungp</name>\n <email>gayoungp@uw.edu</email> ...
## [10] <openSearch:totalResults>8</openSearch:totalResults>
## [11] <openSearch:startIndex>1</openSearch:startIndex>
## [12] <entry>\n <id>https://spreadsheets.google.com/feeds/worksheets/1vF ...
## [13] <entry>\n <id>https://spreadsheets.google.com/feeds/worksheets/1vF ...
## [14] <entry>\n <id>https://spreadsheets.google.com/feeds/worksheets/1vF ...
## [15] <entry>\n <id>https://spreadsheets.google.com/feeds/worksheets/1vF ...
## [16] <entry>\n <id>https://spreadsheets.google.com/feeds/worksheets/1vF ...
## [17] <entry>\n <id>https://spreadsheets.google.com/feeds/worksheets/1vF ...
## [18] <entry>\n <id>https://spreadsheets.google.com/feeds/worksheets/1vF ...
## [19] <entry>\n <id>https://spreadsheets.google.com/feeds/worksheets/1vF ...
content %>% lapply(xml_children) %>% lapply(length)
## $`WI15 ARCHY 499`
## [1] 19
##
## $Gapminder
## [1] 16
##
## $`unitables2010final copy`
## [1] 58
##
## $`ari copy`
## [1] 12
Interesting! There is variability in the number of nodes. What varies?
f <- . %>% xml_children %>% xml_name
possible_nodes <- content %>% lapply(f) %>% unlist() %>% unique()
g <- . %>% xml_children %>% xml_name %>%
factor(levels = possible_nodes) %>% table
#knitr::kable(sapply(content, g))
knitr::kable(sapply(content, g), format = "html",
table.attr = "style='width:30%;'")
WI15 ARCHY 499
Gapminder
unitables2010final copy
ari copy
id
1
1
1
1
updated
1
1
1
1
category
1
1
1
1
title
1
1
1
1
link
4
4
3
4
author
1
1
1
1
totalResults
1
1
1
1
startIndex
1
1
1
1
entry
8
5
48
1
The variation is in the multiplicity of link
and entry
elements. We pursue that below, but let's inspect the more boring components before we move on. We can predict what some of this stuff is based on what we saw in the spreadsheets feed. I'm also going to check if the info here agrees with the spreadsheets feed.
f <- function(x, xpath) xml_find_first(x, xpath, ns_ws) %>% xml_text()
wsf_stuff <-
data_frame(title = sapply(content, f, "feed:title"),
updated = sapply(content, f, "feed:updated"),
author = sapply(content, f, "feed:author//feed:name"),
email = sapply(content, f, "feed:author//feed:email"),
totalResults = sapply(content, f, "openSearch:totalResults"),
startIndex = sapply(content, f, "openSearch:startIndex"))
knitr::kable(wsf_stuff %>% select(title, updated, author, email),
format = "html", table.attr = "style='width:80%;'")
title
updated
author
email
WI15 ARCHY 499
2015-04-13T20:14:47.253Z
gayoungp
gayoungp@uw.edu
Gapminder
2015-03-23T20:34:08.979Z
gspreadr
gspreadr@gmail.com
unitables2010final copy
2009-06-02T09:31:06.582Z
Guardian.facts
guardian.facts@googlemail.com
ari copy
2015-05-19T23:00:13.476Z
gspreadr
gspreadr@gmail.com
#knitr::kable(wsf_stuff %>% select(title, totalResults, startIndex))
So does updated
from a sheet's worksheets feed match updated
from a sheet's entry in the spreadsheets feed?
date_stuff <-
data_frame(sheet_title = examples$sheet_title,
ssf_up = examples$updated %>%
as.POSIXct(format = "%Y-%m-%dT%H:%M:%S", tz = "UTC"),
wsf_header_last_mod =
sapply(req_list, function(x) x$headers$`last-modified`) %>%
httr::parse_http_date(),
wsf_up = wsf_stuff$updated %>%
as.POSIXct(format = "%Y-%m-%dT%H:%M:%S", tz = "UTC"),
wsf_header_date = sapply(req_list, function(x) x$headers$date) %>%
httr::parse_http_date())
knitr::kable(date_stuff, format = "html", table.attr = "style='width:80%;'")
sheet_title
ssf_up
wsf_header_last_mod
wsf_up
wsf_header_date
WI15 ARCHY 499
2015-04-13 20:14:47
2015-04-13 20:14:47
2015-04-13 20:14:47
2015-05-19 23:16:23
Gapminder
2015-03-23 20:59:10
2015-03-23 20:34:08
2015-03-23 20:34:08
2015-05-19 23:16:24
unitables2010final copy
2009-06-02 09:31:06
2009-06-02 09:31:06
2009-06-02 09:31:06
2015-05-19 23:16:24
ari copy
2015-05-19 23:00:13
2015-05-19 23:00:13
2015-05-19 23:00:13
2015-05-19 23:16:25
Strictly "by eye" and for these examples only, I see this:
updated
is the same as the last-modified
field of the headerdate
field of the header refers to the date-time of the GET
request to the worksheets feedupdated
from the spreadsheets feed is usually equal to updated
from the worksheets feed, but not always (see ari copy
for a slight difference)Let's compare author
name between the spreadsheets and worksheets feed.
author_stuff <-
data_frame(sheet_title = examples$sheet_title,
ssf_author = examples$author,
wsf_author_name = wsf_stuff$author)
knitr::kable(author_stuff, format = "html", table.attr = "style='width:80%;'")
sheet_title
ssf_author
wsf_author_name
WI15 ARCHY 499
gayoungp
gayoungp
Gapminder
gspreadr
gspreadr
unitables2010final copy
guardian.facts
Guardian.facts
ari copy
gspreadr
gspreadr
They agree ... well, except for the case. Weird.
Now let's look at totalResults
and startIndex
. I already imagine that totalResults
refers to the number of worksheets and will therefore compare it to the number of entry
nodes.
more_stuff <-
data_frame(sheet_title = examples$sheet_title,
n_entries = content %>%
lapply(xml_find_all,"feed:entry", ns_ws) %>% sapply(length),
wsf_totalResults = wsf_stuff$totalResults,
wsf_startIndex = wsf_stuff$startIndex)
knitr::kable(more_stuff, format = "html", table.attr = "style='width:80%;'")
sheet_title
n_entries
wsf_totalResults
wsf_startIndex
WI15 ARCHY 499
8
8
1
Gapminder
5
5
1
unitables2010final copy
48
48
1
ari copy
1
1
1
YES totalResults
appears to be the number of entry
elements, which is the number of worksheets or tabs. startIndex
remains a useless mystery. From some unrelated fiddling, I think it's a feature common to various Google APIs? I wonder if it only becomes meaningful in a paginated context?
For each example sheet, we grab the id
and the link
nodes and prepare as we did all the links from the spreadsheets feed.
jfun <- function(x) { # gymnastics required for one sheet's worth of links
links <- x %>%
xml_find_all("feed:link", ns_ws) %>%
lapply(xml_attrs) %>%
lapply(as.list) %>%
lapply(as_data_frame) %>%
bind_rows() %>%
mutate(source = "content/entry/link")
links %>%
rbind(data.frame(rel = NA, type = NA,
href = x %>% xml_find_first("feed:id", ns_ws) %>% xml_text(),
source = "content/entry/id")) %>%
mutate(feed = "ws",
sheet_title = x %>% xml_find_first("feed:title", ns_ws)
%>% xml_text()) %>%
select(sheet_title, feed, source, href, rel, type)
}
wsf_links <- content %>% lapply(jfun) %>% bind_rows()
wsf_links_table <- wsf_links %>%
count(rel, sheet_title) %>%
tidyr::spread(sheet_title, n)
knitr::kable(wsf_links_table, format = "html",
table.attr = "style='width:30%;'")
rel
ari copy
Gapminder
unitables2010final copy
WI15 ARCHY 499
alternate
1
1
1
1
http://schemas.google.com/g/2005#feed
1
1
1
1
http://schemas.google.com/g/2005#post
1
1
NA
1
self
1
1
1
1
NA
1
1
1
1
So we always have links with these rel
attributes: "alternate", "self", and "http://schemas.google.com/g/2005#feed". We also have a link with rel
attribute "http://schemas.google.com/g/2005#post" for all but unitables2010final copy
, which is an old sheet for which we only have read access. Note: the link with rel = NA
here is the id
link from the top-level of the worksheets feed.
What relationship do these links have to those from the top-level of the worksheets feed and from the spreadsheets feed?
identical(wsf_links$href[wsf_links$rel %in% "self"], examples$ws_feed)
## [1] TRUE
all.equal(req_list %>% sapply(`[[`, "url"),
examples$ws_feed, check.names = FALSE)
## [1] TRUE
The "self" link in the worksheets feed gives the URL of the sheet's worksheets feed (the link labelled as "http://schemas.google.com/spreadsheets/2006#worksheetsfeed" in the spreadsheets feed), which is also the url
component of the worksheets feed. Confused yet?
wsf_links$href[wsf_links$rel %in% "alternate"] == examples$alternate
## [1] TRUE TRUE FALSE FALSE
It gets worse! The "alternate" link in the worksheets feed is the same as the "alternate" link in the spreadsheet feed __for new sheets_. For old sheets, these two links have the same structure but the sheet key differs. The "alternate" link from the spreadsheets feed contains what I call the alternate key, which is needed for the Google Drive API, whereas the "alternate" link from the worksheets feed uses what I just call the sheet's key. Which is what we use when talking to the Sheets API.
I can find no references in the package's current code to the worksheet feed links labelled as "http://schemas.google.com/g/2005#feed" and "http://schemas.google.com/g/2005#post", so I'm not going to delve into them.
the "worksheets feed" can be found in ...
url in worksheets feed =
id inside the *content* of the worksheets feed =
link named "self" inside the *content* of the worksheets feed =
link named "http://schemas.google.com/spreadsheets/2006#worksheetsfeed" inside the corresponding entry inside the content of the spreadsheets feed
https://spreadsheets.google.com/feeds/worksheets/KEY/VISIBILITY/PERMISSION
VISIBILITY = {private (default), public}
PERMISSION = {full (read and write), values (read only)}
the "alternate" link can be found in ...
link with `rel` attribute "alternate" in the corresponding `entry` inside the content of the spreadsheets feed
link with `rel` attribute "alternate" inside the content of the worksheets feed
for new sheets, the "alternate" link looks like this in both places:
https://docs.google.com/spreadsheets/d/KEY/edit
for old sheets, the "alternate" link looks like this:
https://spreadsheets.google.com/ccc?key=SOME_SORT_OF_KEY
where SOME_SORT_OF_KEY = ALT_KEY in the spreadsheets feed and
SOME_SORT_OF_KEY = KEY in the worksheets feed
Add some info from the spreadsheets feed to the worksheets feed links. Then row bind into one large table of links.
wsf_links <- wsf_links %>%
left_join(links %>%
filter(source == "content/entry/id") %>%
select(sheet_title, version, perm,
link_key, alt_key, sheet_key),
by = "sheet_title")
links <- bind_rows(links %>% filter(sheet_title %in% examples$sheet_title),
wsf_links) %>%
arrange(version, perm, sheet_title, feed, source, rel)
Explore.
links %>%
group_by(sheet_title) %>%
summarise(n = n(), ss = sum(feed == "ss"), ws = sum(feed == "ws"),
ndist = n_distinct(href), version = version[1], perm = perm[1])
## Source: local data frame [4 x 7]
##
## sheet_title n ss ws ndist version perm
## 1 Gapminder 9 4 5 3 new rw
## 2 WI15 ARCHY 499 9 4 5 3 new r
## 3 ari copy 9 4 5 4 old rw
## 4 unitables2010final copy 8 4 4 4 old r
Among these examples, there are only 3 distinct URLs (new sheets) or 4 (old sheets). What are they?
plyr::dlply(links, ~ sheet_title + href, function(x) {
x %>% select(feed, source, rel, version, perm)
})
I'm struggling to make this presentable in this report. For now, just reporting what I see in these results.
For a new sheet, the three URLs are:
the "alternate" link, found in content/entry/link of ss and ws feeds:
https://docs.google.com/spreadsheets/d/KEY/edit
this link is never really used for anything
the spreadsheets link:
https://spreadsheets.google.com/feeds/spreadsheets/private/full/KEY
this link is never really used for anything
the worksheets feed:
https://spreadsheets.google.com/feeds/worksheets/KEY/private/full
this link is critical and is stored redundantly in several places
feed source rel
ss content/entry/link http://...2006#worksheetsfeed
ws content/entry/id <NA>
ws content/entry/link http://schemas.google.com/g/2005#feed
ws content/entry/link http://schemas.google.com/g/2005#post
ws content/entry/link self
For an old sheet, there are four URLs instead of three, because the "alternate" links in the spreadsheets and worksheets feed contain different keys:
two "alternate" links, found in content/entry/link of ss and ws feeds:
spreadsheets feed: https://spreadsheets.google.com/ccc?key=ALT_KEY
worksheets feed: https://spreadsheets.google.com/ccc?key=KEY
we use the alternate link from the spreadsheets feed to get ALT_KEY
the spreadsheets link:
https://spreadsheets.google.com/feeds/spreadsheets/private/full/KEY
this link is never really used for anything
the worksheets feed:
https://spreadsheets.google.com/feeds/worksheets/KEY/private/full
this link is critical and is stored redundantly in several places
(there is no 2005#post link for a real-only old sheet)
feed source rel
ss content/entry/link http://...2006#worksheetsfeed
ws content/entry/id <NA>
ws content/entry/link http://schemas.google.com/g/2005#feed
ws content/entry/link http://schemas.google.com/g/2005#post
ws content/entry/link self
The entry
components correspond to worksheets within the sheet. As we did with content
, we make a list with one component per spreadsheet, each containing another list of the sheet's entry
elements.
ws_entries <- content %>% lapply(xml_find_all, "feed:entry", ns_ws)
ws_entries %>% lapply(length)
## $`WI15 ARCHY 499`
## [1] 8
##
## $Gapminder
## [1] 5
##
## $`unitables2010final copy`
## [1] 48
##
## $`ari copy`
## [1] 1
f <- . %>% xml_children %>% xml_name
possible_nodes <- ws_entries %>% lapply(f) %>% unlist() %>% unique()
g <- . %>% xml_children %>% xml_name %>%
factor(levels = possible_nodes) %>% table
knitr::kable(sapply(ws_entries, g), format = "html",
table.attr = "style='width:30%;'")
WI15 ARCHY 499
Gapminder
unitables2010final copy
ari copy
id
8
5
48
1
updated
8
5
48
1
category
8
5
48
1
title
8
5
48
1
content
8
5
48
1
link
40
30
192
5
colCount
8
5
48
1
rowCount
8
5
48
1
The links are our main interest. But first let's look at the titles, colCounts, and rowCounts.
ws_entries %>% lapply(xml_find_all, "feed:title", ns_ws) %>% lapply(xml_text)
ws_entries %>% lapply(xml_find_all, "gs:rowCount", ns_ws) %>% lapply(xml_text)
ws_entries %>% lapply(xml_find_all, "gs:colCount", ns_ws) %>% lapply(xml_text)
Let's look at the links now. I'm going to work with the links for the first worksheet from each spreadsheet and count on the rest to have similar structure.
ws_links <- ws_entries %>%
lapply(`[`, 1) %>%
lapply(xml_find_all, "feed:link", ns_ws)
ws_links %>% sapply(length)
## WI15 ARCHY 499 Gapminder unitables2010final copy
## 5 6 4
## ari copy
## 5
examples %>% select(sheet_title, perm, version)
## Source: local data frame [4 x 3]
##
## sheet_title perm version
## 1 WI15 ARCHY 499 r new
## 2 Gapminder rw new
## 3 unitables2010final copy r old
## 4 ari copy rw old
The number of links per worksheet is maximized for a "read and write" new sheet: 6 links per worksheet. There are 5 links per worksheet in the cases of a "read only" new sheet and a "read and write" old sheet. There are only 4 links per worksheet for a "read only" old sheet.
jfun <- function(x) { # gymnastics required for one sheet's worth of links
links <- x %>%
xml_find_all("feed:entry", ns_ws) %>%
`[`(1) %>%
xml_find_all("feed:link", ns_ws) %>%
lapply(xml_attrs) %>%
lapply(as.list) %>%
lapply(as_data_frame) %>%
bind_rows()
links %>%
mutate(sheet_title = x %>%
xml_find_first("feed:title", ns_ws) %>% xml_text()) %>%
select(sheet_title, href, rel, type)
}
one_ws_links <-
content %>%
lapply(jfun) %>%
bind_rows() %>%
mutate(rel = rel %>% basename)
one_ws_links_table <- one_ws_links %>%
count(rel, sheet_title) %>%
tidyr::spread(sheet_title, n)
knitr::kable(one_ws_links_table, format = "html",
table.attr = "style='width:30%;'")
rel
ari copy
Gapminder
unitables2010final copy
WI15 ARCHY 499
2006#cellsfeed
1
1
1
1
2006#exportcsv
NA
1
NA
1
2006#listfeed
1
1
1
1
2008#visualizationApi
1
1
1
1
edit
1
1
NA
NA
self
1
1
1
1
The old sheets (ari copy
and unitables2010final copy
) are lacking the 2006#exportcsv, a fact we know all too well. And the "read only" sheets are missing the "edit" link, which stands to reason.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.