| OLD | NEW | 
|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be | 
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. | 
| 4 | 4 | 
| 5 #include "chrome/browser/chromeos/gdata/gdata_parser.h" | 5 #include "chrome/browser/chromeos/gdata/gdata_parser.h" | 
| 6 | 6 | 
| 7 #include "base/basictypes.h" | 7 #include "base/basictypes.h" | 
| 8 #include "base/scoped_ptr.h" | 8 #include "base/json/json_value_converter.h" | 
|  | 9 #include "base/memory/scoped_ptr.h" | 
| 9 #include "base/string_number_conversions.h" | 10 #include "base/string_number_conversions.h" | 
|  | 11 #include "base/string_piece.h" | 
| 10 #include "base/string_util.h" | 12 #include "base/string_util.h" | 
| 11 #include "base/values.h" | 13 #include "base/values.h" | 
| 12 | 14 | 
| 13 using base::Value; | 15 using base::Value; | 
| 14 using base::DictionaryValue; | 16 using base::DictionaryValue; | 
| 15 using base::ListValue; | 17 using base::ListValue; | 
| 16 | 18 | 
| 17 namespace gdata { | 19 namespace gdata { | 
| 18 | 20 | 
| 19 namespace { | 21 namespace { | 
| 20 | 22 | 
| 21 // Term values for kSchemeKind category: | 23 // Term values for kSchemeKind category: | 
| 22 const char kSchemeKind[] = "http://schemas.google.com/g/2005#kind"; | 24 const char kSchemeKind[] = "http://schemas.google.com/g/2005#kind"; | 
| 23 const char kTermPrefix[] = "http://schemas.google.com/docs/2007#"; | 25 const char kTermPrefix[] = "http://schemas.google.com/docs/2007#"; | 
| 24 const char kFileTerm[] = "file"; | 26 const char kFileTerm[] = "file"; | 
| 25 const char kFolderTerm[] = "folder"; | 27 const char kFolderTerm[] = "folder"; | 
| 26 const char kItemTerm[] = "item"; | 28 const char kItemTerm[] = "item"; | 
| 27 const char kPdfTerm[] = "pdf"; | 29 const char kPdfTerm[] = "pdf"; | 
| 28 const char kDocumentTerm[] = "document"; | 30 const char kDocumentTerm[] = "document"; | 
| 29 const char kSpreadSheetTerm[] = "spreadsheet"; | 31 const char kSpreadSheetTerm[] = "spreadsheet"; | 
| 30 const char kPresentationTerm[] = "presentation"; | 32 const char kPresentationTerm[] = "presentation"; | 
| 31 | 33 | 
| 32 const char kSchemeLabels[] = "http://schemas.google.com/g/2005/labels"; | 34 const char kSchemeLabels[] = "http://schemas.google.com/g/2005/labels"; | 
| 33 | 35 | 
| 34 const struct { | 36 struct EntryKindMap { | 
| 35   DocumentEntry::EntryKind kind; | 37   DocumentEntry::EntryKind kind; | 
| 36   const char* entry; | 38   const char* entry; | 
| 37 } kEntryKindMap[] = { | 39 }; | 
|  | 40 | 
|  | 41 const EntryKindMap kEntryKindMap[] = { | 
| 38     { DocumentEntry::ITEM,         "item" }, | 42     { DocumentEntry::ITEM,         "item" }, | 
| 39     { DocumentEntry::DOCUMENT,     "document"}, | 43     { DocumentEntry::DOCUMENT,     "document"}, | 
| 40     { DocumentEntry::SPREADSHEET,  "spreadsheet" }, | 44     { DocumentEntry::SPREADSHEET,  "spreadsheet" }, | 
| 41     { DocumentEntry::PRESENTATION, "presentation" }, | 45     { DocumentEntry::PRESENTATION, "presentation" }, | 
| 42     { DocumentEntry::FOLDER,       "folder"}, | 46     { DocumentEntry::FOLDER,       "folder"}, | 
| 43     { DocumentEntry::FILE,         "file"}, | 47     { DocumentEntry::FILE,         "file"}, | 
| 44     { DocumentEntry::PDF,          "pdf"}, | 48     { DocumentEntry::PDF,          "pdf"}, | 
| 45     { DocumentEntry::UNKNOWN,      NULL} |  | 
| 46 }; | 49 }; | 
| 47 | 50 | 
| 48 const struct { | 51 struct LinkTypeMap { | 
| 49   Link::LinkType type; | 52   Link::LinkType type; | 
| 50   const char* rel; | 53   const char* rel; | 
| 51 } kLinkTypeMap[] = { | 54 }; | 
|  | 55 | 
|  | 56 const LinkTypeMap kLinkTypeMap[] = { | 
| 52     { Link::SELF, | 57     { Link::SELF, | 
| 53       "self" }, | 58       "self" }, | 
| 54     { Link::NEXT, | 59     { Link::NEXT, | 
| 55       "next" }, | 60       "next" }, | 
| 56     { Link::PARENT, | 61     { Link::PARENT, | 
| 57       "http://schemas.google.com/docs/2007#parent" }, | 62       "http://schemas.google.com/docs/2007#parent" }, | 
| 58     { Link::ALTERNATE, | 63     { Link::ALTERNATE, | 
| 59       "alternate"}, | 64       "alternate"}, | 
| 60     { Link::EDIT, | 65     { Link::EDIT, | 
| 61       "edit" }, | 66       "edit" }, | 
| 62     { Link::EDIT_MEDIA, | 67     { Link::EDIT_MEDIA, | 
| 63       "edit-media" }, | 68       "edit-media" }, | 
| 64     { Link::FEED, | 69     { Link::FEED, | 
| 65       "http://schemas.google.com/g/2005#feed"}, | 70       "http://schemas.google.com/g/2005#feed"}, | 
| 66     { Link::POST, | 71     { Link::POST, | 
| 67       "http://schemas.google.com/g/2005#post"}, | 72       "http://schemas.google.com/g/2005#post"}, | 
| 68     { Link::BATCH, | 73     { Link::BATCH, | 
| 69       "http://schemas.google.com/g/2005#batch"}, | 74       "http://schemas.google.com/g/2005#batch"}, | 
| 70     { Link::THUMBNAIL, | 75     { Link::THUMBNAIL, | 
| 71       "http://schemas.google.com/docs/2007/thumbnail"}, | 76       "http://schemas.google.com/docs/2007/thumbnail"}, | 
| 72     { Link::RESUMABLE_EDIT_MEDIA, | 77     { Link::RESUMABLE_EDIT_MEDIA, | 
| 73       "http://schemas.google.com/g/2005#resumable-edit-media"}, | 78       "http://schemas.google.com/g/2005#resumable-edit-media"}, | 
| 74     { Link::RESUMABLE_CREATE_MEDIA, | 79     { Link::RESUMABLE_CREATE_MEDIA, | 
| 75       "http://schemas.google.com/g/2005#resumable-create-media"}, | 80       "http://schemas.google.com/g/2005#resumable-create-media"}, | 
| 76     { Link::TABLES_FEED, | 81     { Link::TABLES_FEED, | 
| 77       "http://schemas.google.com/spreadsheets/2006#tablesfeed"}, | 82       "http://schemas.google.com/spreadsheets/2006#tablesfeed"}, | 
| 78     { Link::WORKSHEET_FEED, | 83     { Link::WORKSHEET_FEED, | 
| 79       "http://schemas.google.com/spreadsheets/2006#worksheetsfeed"}, | 84       "http://schemas.google.com/spreadsheets/2006#worksheetsfeed"}, | 
| 80     { Link::UNKNOWN, |  | 
| 81       NULL} |  | 
| 82 }; | 85 }; | 
| 83 | 86 | 
| 84 const struct { | 87 struct FeedLinkTypeMap { | 
| 85   FeedLink::FeedLinkType type; | 88   FeedLink::FeedLinkType type; | 
| 86   const char* rel; | 89   const char* rel; | 
| 87 } kFeedLinkTypeMap[] = { | 90 }; | 
|  | 91 | 
|  | 92 const FeedLinkTypeMap kFeedLinkTypeMap[] = { | 
| 88     { FeedLink::ACL, | 93     { FeedLink::ACL, | 
| 89       "http://schemas.google.com/acl/2007#accessControlList" }, | 94       "http://schemas.google.com/acl/2007#accessControlList" }, | 
| 90     { FeedLink::REVISIONS, | 95     { FeedLink::REVISIONS, | 
| 91       "http://schemas.google.com/docs/2007/revisions" }, | 96       "http://schemas.google.com/docs/2007/revisions" }, | 
| 92     { FeedLink::UNKNOWN, |  | 
| 93       NULL} |  | 
| 94 }; | 97 }; | 
| 95 | 98 | 
| 96 const struct { | 99 struct CategoryTypeMap { | 
| 97   Category::CategoryType type; | 100   Category::CategoryType type; | 
| 98   const char* scheme; | 101   const char* scheme; | 
| 99 } kCategoryTypeMap[] = { | 102 }; | 
|  | 103 | 
|  | 104 const CategoryTypeMap kCategoryTypeMap[] = { | 
| 100     { Category::KIND, | 105     { Category::KIND, | 
| 101       "http://schemas.google.com/g/2005#kind" }, | 106       "http://schemas.google.com/g/2005#kind" }, | 
| 102     { Category::LABEL, | 107     { Category::LABEL, | 
| 103       "http://schemas.google.com/g/2005/labels" }, | 108       "http://schemas.google.com/g/2005/labels" }, | 
| 104     { Category::UNKNOWN, |  | 
| 105       NULL} |  | 
| 106 }; | 109 }; | 
| 107 | 110 | 
|  | 111 // Converts |url_string| to |result|.  Always returns true to be used | 
|  | 112 // for JSONValueConverter::RegisterCustomField method. | 
|  | 113 // TODO(mukai): make it return false in case of invalid |url_string|. | 
|  | 114 bool GetGURLFromString(const base::StringPiece& url_string, GURL* result) { | 
|  | 115   *result = GURL(url_string.as_string()); | 
|  | 116   return true; | 
|  | 117 } | 
|  | 118 | 
| 108 }  // namespace | 119 }  // namespace | 
| 109 | 120 | 
| 110 const char Author::kNameField[] = "name.$t"; | 121 const char Author::kNameField[] = "name.$t"; | 
| 111 const char Author::kEmailField[] = "email.$t"; | 122 const char Author::kEmailField[] = "email.$t"; | 
| 112 | 123 | 
| 113 Author::Author() { | 124 Author::Author() { | 
| 114 } | 125 } | 
| 115 | 126 | 
| 116 bool Author::Parse(const DictionaryValue* dictionary) { | 127 // static | 
| 117   if (dictionary->GetString(kNameField, &name_) && | 128 void Author::RegisterJSONConverter( | 
| 118       dictionary->GetString(kEmailField, &email_)) { | 129     base::JSONValueConverter<Author>* converter) { | 
| 119     return true; | 130   converter->RegisterStringField(kNameField, &Author::name_); | 
| 120   } | 131   converter->RegisterStringField(kEmailField, &Author::email_); | 
| 121   return false; |  | 
| 122 } | 132 } | 
| 123 | 133 | 
| 124 const char Link::kHrefField[] = "href"; | 134 const char Link::kHrefField[] = "href"; | 
| 125 const char Link::kRelField[] = "rel"; | 135 const char Link::kRelField[] = "rel"; | 
| 126 const char Link::kTitleField[] = "title"; | 136 const char Link::kTitleField[] = "title"; | 
| 127 const char Link::kTypeField[] = "type"; | 137 const char Link::kTypeField[] = "type"; | 
| 128 | 138 | 
| 129 Link::Link() : type_(Link::UNKNOWN) { | 139 Link::Link() : type_(Link::UNKNOWN) { | 
| 130 } | 140 } | 
| 131 | 141 | 
| 132 bool Link::Parse(const DictionaryValue* dictionary) { | 142 // static. | 
| 133   std::string rel; | 143 bool Link::GetLinkType(const base::StringPiece& rel, Link::LinkType* result) { | 
| 134   std::string href; | 144   for (size_t i = 0; i < arraysize(kLinkTypeMap); i++) { | 
| 135   if (dictionary->GetString(kRelField, &rel) && | 145     if (rel == kLinkTypeMap[i].rel) { | 
| 136       dictionary->GetString(kHrefField, &href) && | 146       *result = kLinkTypeMap[i].type; | 
| 137       dictionary->GetString(kTypeField, &mime_type_)) { |  | 
| 138     href_ = GURL(href); |  | 
| 139     type_ = GetLinkType(rel); |  | 
| 140     if (type_ == Link::PARENT) |  | 
| 141       dictionary->GetString(kTitleField, &title_); |  | 
| 142 |  | 
| 143     if (type_ != Link::UNKNOWN) |  | 
| 144       return true; | 147       return true; | 
|  | 148     } | 
| 145   } | 149   } | 
|  | 150   DVLOG(1) << "Unknown link type for rel " << rel; | 
| 146   return false; | 151   return false; | 
| 147 } | 152 } | 
| 148 | 153 | 
| 149 // static. | 154 // static | 
| 150 Link::LinkType Link::GetLinkType(const std::string& rel) { | 155 void Link::RegisterJSONConverter(base::JSONValueConverter<Link>* converter) { | 
| 151   for (size_t i = 0; kLinkTypeMap[i].rel; i++) { | 156   converter->RegisterCustomField<Link::LinkType>( | 
| 152     if (rel == kLinkTypeMap[i].rel) | 157       kRelField, &Link::type_, &Link::GetLinkType); | 
| 153       return kLinkTypeMap[i].type; | 158   converter->RegisterCustomField(kHrefField, &Link::href_, &GetGURLFromString); | 
| 154   } | 159   converter->RegisterStringField(kTitleField, &Link::title_); | 
| 155   DVLOG(1) << "Unknown link type for rel " << rel; | 160   converter->RegisterStringField(kTypeField, &Link::mime_type_); | 
| 156   return Link::UNKNOWN; |  | 
| 157 } | 161 } | 
| 158 | 162 | 
| 159 const char FeedLink::kHrefField[] = "href"; | 163 const char FeedLink::kHrefField[] = "href"; | 
| 160 const char FeedLink::kRelField[] = "rel"; | 164 const char FeedLink::kRelField[] = "rel"; | 
| 161 | 165 | 
| 162 FeedLink::FeedLink() : type_(FeedLink::UNKNOWN) { | 166 FeedLink::FeedLink() : type_(FeedLink::UNKNOWN) { | 
| 163 } | 167 } | 
| 164 | 168 | 
| 165 bool FeedLink::Parse(const DictionaryValue* dictionary) { | 169 // static. | 
| 166   std::string rel; | 170 bool FeedLink::GetFeedLinkType( | 
| 167   std::string href; | 171     const base::StringPiece& rel, FeedLink::FeedLinkType* result) { | 
| 168   if (dictionary->GetString(kRelField, &rel) && | 172   for (size_t i = 0; i < arraysize(kFeedLinkTypeMap); i++) { | 
| 169       dictionary->GetString(kHrefField, &href)) { | 173     if (rel == kFeedLinkTypeMap[i].rel) { | 
| 170     href_ = GURL(href); | 174       *result = kFeedLinkTypeMap[i].type; | 
| 171     type_ = GetFeedLinkType(rel); |  | 
| 172     if (type_ != FeedLink::UNKNOWN) |  | 
| 173       return true; | 175       return true; | 
|  | 176     } | 
| 174   } | 177   } | 
|  | 178   DVLOG(1) << "Unknown feed link type for rel " << rel; | 
| 175   return false; | 179   return false; | 
| 176 } | 180 } | 
| 177 | 181 | 
| 178 // static. | 182 // static | 
| 179 FeedLink::FeedLinkType FeedLink::GetFeedLinkType(const std::string& rel) { | 183 void FeedLink::RegisterJSONConverter( | 
| 180   for (size_t i = 0; kFeedLinkTypeMap[i].rel; i++) { | 184     base::JSONValueConverter<FeedLink>* converter) { | 
| 181     if (rel == kFeedLinkTypeMap[i].rel) | 185   converter->RegisterCustomField<FeedLink::FeedLinkType>( | 
| 182       return kFeedLinkTypeMap[i].type; | 186       kRelField, &FeedLink::type_, &FeedLink::GetFeedLinkType); | 
| 183   } | 187   converter->RegisterCustomField( | 
| 184   DVLOG(1) << "Unknown feed link type for rel " << rel; | 188       kHrefField, &FeedLink::href_, &GetGURLFromString); | 
| 185   return FeedLink::UNKNOWN; |  | 
| 186 } | 189 } | 
| 187 | 190 | 
| 188 const char Category::kLabelField[] = "label"; | 191 const char Category::kLabelField[] = "label"; | 
| 189 const char Category::kSchemeField[] = "scheme"; | 192 const char Category::kSchemeField[] = "scheme"; | 
| 190 const char Category::kTermField[] = "term"; | 193 const char Category::kTermField[] = "term"; | 
| 191 | 194 | 
| 192 Category::Category() { | 195 Category::Category() : type_(UNKNOWN) { | 
| 193 } | 196 } | 
| 194 | 197 | 
| 195 bool Category::Parse(const DictionaryValue* dictionary) { | 198 // Converts category.scheme into CategoryType enum. | 
| 196   std::string scheme; | 199 bool Category::GetCategoryTypeFromScheme( | 
| 197   if (dictionary->GetString(kLabelField, &label_) && | 200     const base::StringPiece& scheme, Category::CategoryType* result) { | 
| 198       dictionary->GetString(kSchemeField, &scheme) && | 201   for (size_t i = 0; i < arraysize(kCategoryTypeMap); i++) { | 
| 199       dictionary->GetString(kTermField, &term_)) { | 202     if (scheme == kCategoryTypeMap[i].scheme) { | 
| 200     type_ = GetCategoryTypeFromScheme(scheme); | 203       *result = kCategoryTypeMap[i].type; | 
| 201     if (type_ != Category::UNKNOWN) |  | 
| 202       return true; | 204       return true; | 
| 203 | 205     } | 
| 204     DVLOG(1) << "Unknown category:" |  | 
| 205              << "\n  label = " << label_ |  | 
| 206              << "\n  scheme = " << scheme |  | 
| 207              << "\n  term = " << term_; |  | 
| 208   } | 206   } | 
|  | 207   DVLOG(1) << "Unknown feed link type for scheme " << scheme; | 
| 209   return false; | 208   return false; | 
| 210 } | 209 } | 
| 211 | 210 | 
| 212 // Converts category.scheme into CategoryType enum. | 211 // static | 
| 213 Category::CategoryType Category::GetCategoryTypeFromScheme( | 212 void Category::RegisterJSONConverter( | 
| 214     const std::string& scheme) { | 213     base::JSONValueConverter<Category>* converter) { | 
| 215   for (size_t i = 0; kCategoryTypeMap[i].scheme; i++) { | 214   converter->RegisterStringField(kLabelField, &Category::label_); | 
| 216     if (scheme == kCategoryTypeMap[i].scheme) | 215   converter->RegisterCustomField<Category::CategoryType>( | 
| 217       return kCategoryTypeMap[i].type; | 216       kSchemeField, &Category::type_, &Category::GetCategoryTypeFromScheme); | 
| 218   } | 217   converter->RegisterStringField(kTermField, &Category::term_); | 
| 219   DVLOG(1) << "Unknown feed link type for scheme " << scheme; |  | 
| 220   return Category::UNKNOWN; |  | 
| 221 } | 218 } | 
| 222 | 219 | 
| 223 |  | 
| 224 const Link* GDataEntry::GetLinkByType(Link::LinkType type) const { | 220 const Link* GDataEntry::GetLinkByType(Link::LinkType type) const { | 
| 225   for (ScopedVector<Link>::const_iterator iter = links_.begin(); | 221   for (size_t i = 0; i < links_.size(); ++i) { | 
| 226        iter != links_.end(); ++iter) { | 222     if (links_[i]->type() == type) | 
| 227     if ((*iter)->type() == type) | 223       return links_[i]; | 
| 228       return (*iter); |  | 
| 229   } | 224   } | 
| 230   return NULL; | 225   return NULL; | 
| 231 } | 226 } | 
| 232 | 227 | 
|  | 228 const char Content::kSrcField[] = "src"; | 
|  | 229 const char Content::kTypeField[] = "type"; | 
|  | 230 | 
|  | 231 Content::Content() { | 
|  | 232 } | 
|  | 233 | 
|  | 234 // static | 
|  | 235 void Content::RegisterJSONConverter( | 
|  | 236     base::JSONValueConverter<Content>* converter) { | 
|  | 237   converter->RegisterCustomField(kSrcField, &Content::url_, &GetGURLFromString); | 
|  | 238   converter->RegisterStringField(kTypeField, &Content::mime_type_); | 
|  | 239 } | 
|  | 240 | 
| 233 const char GDataEntry::kTimeParsingDelimiters[] = "-:.TZ"; | 241 const char GDataEntry::kTimeParsingDelimiters[] = "-:.TZ"; | 
| 234 const char GDataEntry::kAuthorField[] = "author"; | 242 const char GDataEntry::kAuthorField[] = "author"; | 
| 235 const char GDataEntry::kLinkField[] = "link"; | 243 const char GDataEntry::kLinkField[] = "link"; | 
| 236 const char GDataEntry::kCategoryField[] = "category"; | 244 const char GDataEntry::kCategoryField[] = "category"; | 
|  | 245 const char GDataEntry::kETagField[] = "gd$etag"; | 
|  | 246 const char GDataEntry::kUpdatedField[] = "updated.$t"; | 
| 237 | 247 | 
| 238 GDataEntry::GDataEntry() { | 248 GDataEntry::GDataEntry() { | 
| 239 } | 249 } | 
| 240 | 250 | 
| 241 GDataEntry::~GDataEntry() { | 251 GDataEntry::~GDataEntry() { | 
| 242 } | 252 } | 
| 243 | 253 | 
| 244 bool GDataEntry::ParseAuthors(const DictionaryValue* value_dict) { | 254 // static | 
| 245   ListValue* authors = NULL; | 255 void GDataEntry::RegisterJSONConverter( | 
| 246   if (value_dict->GetList(kAuthorField, &authors)) { | 256     base::JSONValueConverter<GDataEntry>* converter) { | 
| 247     for (ListValue::iterator iter = authors->begin(); | 257   converter->RegisterStringField(kETagField, &GDataEntry::etag_); | 
| 248          iter != authors->end(); | 258   converter->RegisterRepeatedMessage(kAuthorField, &GDataEntry::authors_); | 
| 249          ++iter) { | 259   converter->RegisterRepeatedMessage(kLinkField, &GDataEntry::links_); | 
| 250       if ((*iter)->GetType() != Value::TYPE_DICTIONARY) { | 260   converter->RegisterRepeatedMessage(kCategoryField, &GDataEntry::categories_); | 
| 251         DVLOG(1) << "Invalid author list element"; | 261   converter->RegisterCustomField<base::Time>( | 
| 252         return false; | 262       kUpdatedField, | 
| 253       } | 263       &GDataEntry::updated_time_, | 
| 254       DictionaryValue* author_dict = | 264       &GDataEntry::GetTimeFromString); | 
| 255           reinterpret_cast<DictionaryValue*>(*iter); |  | 
| 256       scoped_ptr<Author> author(new Author()); |  | 
| 257       if (author->Parse(author_dict)) { |  | 
| 258         authors_.push_back(author.release()); |  | 
| 259       } else { |  | 
| 260         DVLOG(1) << "Invalid author etag = " << etag_; |  | 
| 261       } |  | 
| 262     } |  | 
| 263   } |  | 
| 264   return true; |  | 
| 265 } | 265 } | 
| 266 | 266 | 
| 267 bool GDataEntry::ParseLinks(const DictionaryValue* value_dict) { | 267 // static | 
| 268   ListValue* links = NULL; | 268 bool GDataEntry::GetTimeFromString(const base::StringPiece& raw_value, | 
| 269   if (value_dict->GetList(kLinkField, &links)) { |  | 
| 270     for (ListValue::iterator iter = links->begin(); |  | 
| 271          iter != links->end(); |  | 
| 272          ++iter) { |  | 
| 273       if ((*iter)->GetType() != Value::TYPE_DICTIONARY) { |  | 
| 274         DVLOG(1) << "Invalid link list element"; |  | 
| 275         return false; |  | 
| 276       } |  | 
| 277       DictionaryValue* link_dict = |  | 
| 278           reinterpret_cast<DictionaryValue*>(*iter); |  | 
| 279       scoped_ptr<Link> link(new Link()); |  | 
| 280       if (link->Parse(link_dict)) |  | 
| 281         links_.push_back(link.release()); |  | 
| 282       else |  | 
| 283         DVLOG(1) << "Invalid link etag = " << etag_; |  | 
| 284     } |  | 
| 285   } |  | 
| 286   return true; |  | 
| 287 } |  | 
| 288 |  | 
| 289 // static. |  | 
| 290 bool GDataEntry::GetTimeFromString(const std::string& raw_value, |  | 
| 291                                    base::Time* time) { | 269                                    base::Time* time) { | 
| 292   std::vector<std::string> parts; | 270   std::vector<base::StringPiece> parts; | 
| 293   if (Tokenize(raw_value, kTimeParsingDelimiters, &parts) != 7) | 271   if (Tokenize(raw_value, kTimeParsingDelimiters, &parts) != 7) | 
| 294     return false; | 272     return false; | 
| 295 | 273 | 
| 296   base::Time::Exploded exploded; | 274   base::Time::Exploded exploded; | 
| 297   if (!base::StringToInt(parts[0], &exploded.year) || | 275   if (!base::StringToInt(parts[0], &exploded.year) || | 
| 298       !base::StringToInt(parts[1], &exploded.month) || | 276       !base::StringToInt(parts[1], &exploded.month) || | 
| 299       !base::StringToInt(parts[2], &exploded.day_of_month) || | 277       !base::StringToInt(parts[2], &exploded.day_of_month) || | 
| 300       !base::StringToInt(parts[3], &exploded.hour) || | 278       !base::StringToInt(parts[3], &exploded.hour) || | 
| 301       !base::StringToInt(parts[4], &exploded.minute) || | 279       !base::StringToInt(parts[4], &exploded.minute) || | 
| 302       !base::StringToInt(parts[5], &exploded.second) || | 280       !base::StringToInt(parts[5], &exploded.second) || | 
| 303       !base::StringToInt(parts[6], &exploded.millisecond)) { | 281       !base::StringToInt(parts[6], &exploded.millisecond)) { | 
| 304     return false; | 282     return false; | 
| 305   } | 283   } | 
| 306 | 284 | 
| 307   exploded.day_of_week = 0; | 285   exploded.day_of_week = 0; | 
| 308   if (!exploded.HasValidValues()) | 286   if (!exploded.HasValidValues()) | 
| 309     return false; | 287     return false; | 
| 310 | 288 | 
| 311   *time = base::Time::FromLocalExploded(exploded); | 289   *time = base::Time::FromLocalExploded(exploded); | 
| 312   return true; | 290   return true; | 
| 313 } | 291 } | 
| 314 | 292 | 
| 315 bool GDataEntry::ParseCategories(const DictionaryValue* value_dict) { |  | 
| 316   ListValue* categories = NULL; |  | 
| 317   if (value_dict->GetList(kCategoryField, &categories)) { |  | 
| 318     for (ListValue::iterator iter = categories->begin(); |  | 
| 319          iter != categories->end(); |  | 
| 320          ++iter) { |  | 
| 321       if ((*iter)->GetType() != Value::TYPE_DICTIONARY) { |  | 
| 322         DVLOG(1) << "Invalid category list element"; |  | 
| 323         return false; |  | 
| 324       } |  | 
| 325       DictionaryValue* category_dict = |  | 
| 326           reinterpret_cast<DictionaryValue*>(*iter); |  | 
| 327       scoped_ptr<Category> category(new Category()); |  | 
| 328       if (category->Parse(category_dict)) { |  | 
| 329         OnAddCategory(category.get()); |  | 
| 330         categories_->push_back(category.release()); |  | 
| 331       } else { |  | 
| 332         DVLOG(1) << "Invalid category etag = " << etag_; |  | 
| 333       } |  | 
| 334     } |  | 
| 335   } |  | 
| 336   return true; |  | 
| 337 } |  | 
| 338 |  | 
| 339 // static. |  | 
| 340 bool GDataEntry::ParseDateTime(const DictionaryValue* dict, |  | 
| 341                                const std::string& field, |  | 
| 342                                base::Time* time) { |  | 
| 343   std::string raw_value; |  | 
| 344   if (!dict->GetString(field, &raw_value)) |  | 
| 345     return false; |  | 
| 346 |  | 
| 347   return GetTimeFromString(raw_value, time); |  | 
| 348 } |  | 
| 349 |  | 
| 350 const char DocumentEntry::kFeedLinkField[] = "gd$feedLink"; | 293 const char DocumentEntry::kFeedLinkField[] = "gd$feedLink"; | 
| 351 const char DocumentEntry::kContentField[] = "content"; | 294 const char DocumentEntry::kContentField[] = "content"; | 
| 352 const char DocumentEntry::kSrcField[] = "src"; |  | 
| 353 const char DocumentEntry::kTypeField[] = "type"; |  | 
| 354 const char DocumentEntry::kFileNameField[] = "docs$filename.$t"; | 295 const char DocumentEntry::kFileNameField[] = "docs$filename.$t"; | 
| 355 const char DocumentEntry::kMD5Field[] = "docs$md5Checksum.$t"; | 296 const char DocumentEntry::kMD5Field[] = "docs$md5Checksum.$t"; | 
| 356 const char DocumentEntry::kSizeField[] = "docs$size.$t"; | 297 const char DocumentEntry::kSizeField[] = "docs$size.$t"; | 
| 357 const char DocumentEntry::kSuggestedFileNameField[] = | 298 const char DocumentEntry::kSuggestedFileNameField[] = | 
| 358     "docs$suggestedFilename.$t"; | 299     "docs$suggestedFilename.$t"; | 
| 359 const char DocumentEntry::kETagField[] = "gd$etag"; |  | 
| 360 const char DocumentEntry::kResourceIdField[] = "gd$resourceId.$t"; | 300 const char DocumentEntry::kResourceIdField[] = "gd$resourceId.$t"; | 
| 361 const char DocumentEntry::kIDField[] = "id.$t"; | 301 const char DocumentEntry::kIDField[] = "id.$t"; | 
| 362 const char DocumentEntry::kTitleField[] = "title.$t"; | 302 const char DocumentEntry::kTitleField[] = "title.$t"; | 
| 363 const char DocumentEntry::kUpdatedField[] = "updated.$t"; |  | 
| 364 const char DocumentEntry::kPublishedField[] = "published.$t"; | 303 const char DocumentEntry::kPublishedField[] = "published.$t"; | 
| 365 | 304 | 
| 366 DocumentEntry::DocumentEntry() : kind_(DocumentEntry::UNKNOWN), file_size_(0) { | 305 DocumentEntry::DocumentEntry() : kind_(DocumentEntry::UNKNOWN), file_size_(0) { | 
| 367 } | 306 } | 
| 368 | 307 | 
| 369 DocumentEntry::~DocumentEntry() { | 308 DocumentEntry::~DocumentEntry() { | 
| 370 } | 309 } | 
| 371 | 310 | 
| 372 bool DocumentEntry::ParseFeedLinks(const DictionaryValue* value_dict) { | 311 // static | 
| 373   ListValue* links = NULL; | 312 void DocumentEntry::RegisterJSONConverter( | 
| 374   if (value_dict->GetList(kFeedLinkField, &links)) { | 313     base::JSONValueConverter<DocumentEntry>* converter) { | 
| 375     for (ListValue::iterator iter = links->begin(); | 314   // inheritant the parent registrations. | 
| 376          iter != links->end(); | 315   GDataEntry::RegisterJSONConverter( | 
| 377          ++iter) { | 316       reinterpret_cast<base::JSONValueConverter<GDataEntry>*>(converter)); | 
| 378       if ((*iter)->GetType() != Value::TYPE_DICTIONARY) { | 317   converter->RegisterStringField( | 
| 379         DVLOG(1) << "Invalid feed link list element"; | 318       kResourceIdField, &DocumentEntry::resource_id_); | 
| 380         return false; | 319   converter->RegisterStringField(kIDField, &DocumentEntry::id_); | 
| 381       } | 320   converter->RegisterStringField(kTitleField, &DocumentEntry::title_); | 
| 382       DictionaryValue* link_dict = | 321   converter->RegisterCustomField<base::Time>( | 
| 383           reinterpret_cast<DictionaryValue*>(*iter); | 322       kPublishedField, &DocumentEntry::published_time_, | 
| 384       scoped_ptr<FeedLink> link(new FeedLink()); | 323       &GDataEntry::GetTimeFromString); | 
| 385       if (link->Parse(link_dict)) { | 324   converter->RegisterRepeatedMessage( | 
| 386         feed_links_.push_back(link.release()); | 325       kFeedLinkField, &DocumentEntry::feed_links_); | 
| 387       } else { | 326   converter->RegisterNestedField(kContentField, &DocumentEntry::content_); | 
| 388         DVLOG(1) << "Invalid feed link etag = " << etag_; | 327 | 
| 389       } | 328   // File properties.  If the document type is not a normal file, then | 
| 390     } | 329   // that's no problem because those feed must not have these fields | 
| 391   } | 330   // themselves, which does not report errors. | 
| 392   return true; | 331   converter->RegisterStringField(kFileNameField, &DocumentEntry::filename_); | 
|  | 332   converter->RegisterStringField(kMD5Field, &DocumentEntry::file_md5_); | 
|  | 333   converter->RegisterCustomField<int64>( | 
|  | 334       kSizeField, &DocumentEntry::file_size_, &base::StringToInt64); | 
|  | 335   converter->RegisterStringField( | 
|  | 336       kSuggestedFileNameField, &DocumentEntry::suggested_filename_); | 
| 393 } | 337 } | 
| 394 | 338 | 
| 395 bool DocumentEntry::ParseContent(const DictionaryValue* value_dict) { | 339 // static | 
| 396   base::DictionaryValue* content = NULL; |  | 
| 397   if (value_dict->GetDictionary(kContentField, &content)) { |  | 
| 398     std::string src; |  | 
| 399     if (content->GetString(kSrcField, &src) && |  | 
| 400         content->GetString(kTypeField, &content_mime_type_)) { |  | 
| 401       content_url_ = GURL(src); |  | 
| 402       return true; |  | 
| 403     } |  | 
| 404   } |  | 
| 405   DVLOG(1) << "Invalid item content etag = " << etag_; |  | 
| 406   return false; |  | 
| 407 } |  | 
| 408 |  | 
| 409 bool DocumentEntry::ParseFileProperties(const DictionaryValue* value_dict) { |  | 
| 410   if (!value_dict->GetString(kFileNameField, &filename_)) { |  | 
| 411     DVLOG(1) << "File item with no name! " << etag_; |  | 
| 412     return false; |  | 
| 413   } |  | 
| 414 |  | 
| 415   if (!value_dict->GetString(kMD5Field, &file_md5_)) { |  | 
| 416     DVLOG(1) << "File item with no md5! " << etag_; |  | 
| 417     return false; |  | 
| 418   } |  | 
| 419 |  | 
| 420   std::string file_size; |  | 
| 421   if (!value_dict->GetString(kSizeField, &file_size) || |  | 
| 422       !file_size.length()) { |  | 
| 423     DVLOG(1) << "File item with no size! " << etag_; |  | 
| 424     return false; |  | 
| 425   } |  | 
| 426 |  | 
| 427   if (!base::StringToInt64(file_size, &file_size_)) { |  | 
| 428     DVLOG(1) << "Invalid file size '" << file_size << "' for " << etag_; |  | 
| 429     return false; |  | 
| 430   } |  | 
| 431 |  | 
| 432   if (!value_dict->GetString(kSuggestedFileNameField, &suggested_filename_)) |  | 
| 433     DVLOG(1) << "File item with no docs$suggestedFilename! " << etag_; |  | 
| 434 |  | 
| 435   return true; |  | 
| 436 } |  | 
| 437 |  | 
| 438 bool DocumentEntry::Parse(const DictionaryValue* value_dict) { |  | 
| 439   if (!value_dict->GetString(kETagField, &etag_)) { |  | 
| 440     DVLOG(1) << "Item with no etag!"; |  | 
| 441     return false; |  | 
| 442   } |  | 
| 443 |  | 
| 444   if (!value_dict->GetString(kResourceIdField, &resource_id_)) { |  | 
| 445     DVLOG(1) << "Item with no resource id! " << etag_; |  | 
| 446     return false; |  | 
| 447   } |  | 
| 448 |  | 
| 449   if (!value_dict->GetString(kIDField, &id_)) { |  | 
| 450     DVLOG(1) << "Item with no id! " << etag_; |  | 
| 451     return false; |  | 
| 452   } |  | 
| 453 |  | 
| 454   if (!value_dict->GetString(kTitleField, &title_)) { |  | 
| 455     DVLOG(1) << "Item with no title! " << etag_; |  | 
| 456     return false; |  | 
| 457   } |  | 
| 458 |  | 
| 459   if (!ParseDateTime(value_dict, kUpdatedField, &updated_time_)) { |  | 
| 460     DVLOG(1) << "Item with no updated date! " << etag_; |  | 
| 461     return false; |  | 
| 462   } |  | 
| 463 |  | 
| 464   if (!ParseDateTime(value_dict, kPublishedField, &published_time_)) { |  | 
| 465     DVLOG(1) << "Item with no published date! " << etag_; |  | 
| 466     return false; |  | 
| 467   } |  | 
| 468 |  | 
| 469   // Parse categories, will set up entry->kind as well. |  | 
| 470   if (!ParseCategories(value_dict)) |  | 
| 471     return false; |  | 
| 472 |  | 
| 473   if (kind_ == DocumentEntry::FILE || kind_ == DocumentEntry::PDF) { |  | 
| 474     if (!ParseFileProperties(value_dict)) |  | 
| 475       return false; |  | 
| 476   } |  | 
| 477 |  | 
| 478   if (!ParseAuthors(value_dict)) |  | 
| 479     return false; |  | 
| 480 |  | 
| 481   if (!ParseContent(value_dict)) |  | 
| 482     return false; |  | 
| 483 |  | 
| 484   if (!ParseLinks(value_dict)) |  | 
| 485     return false; |  | 
| 486 |  | 
| 487   if (!ParseFeedLinks(value_dict)) |  | 
| 488     return false; |  | 
| 489 |  | 
| 490   return true; |  | 
| 491 } |  | 
| 492 |  | 
| 493 // static. |  | 
| 494 DocumentEntry::EntryKind DocumentEntry::GetEntryKindFromTerm( | 340 DocumentEntry::EntryKind DocumentEntry::GetEntryKindFromTerm( | 
| 495     const std::string& term) { | 341     const std::string& term) { | 
| 496   if (!StartsWithASCII(term, kTermPrefix, false)) { | 342   if (!StartsWithASCII(term, kTermPrefix, false)) { | 
| 497     DVLOG(1) << "Unexpected term prefix term " << term; | 343     DVLOG(1) << "Unexpected term prefix term " << term; | 
| 498     return DocumentEntry::UNKNOWN; | 344     return DocumentEntry::UNKNOWN; | 
| 499   } | 345   } | 
| 500 | 346 | 
| 501   std::string type = term.substr(strlen(kTermPrefix)); | 347   std::string type = term.substr(strlen(kTermPrefix)); | 
| 502   for (size_t i = 0; kEntryKindMap[i].entry; i++) { | 348   for (size_t i = 0; i < arraysize(kEntryKindMap); i++) { | 
| 503     if (type == kEntryKindMap[i].entry) | 349     if (type == kEntryKindMap[i].entry) | 
| 504       return kEntryKindMap[i].kind; | 350       return kEntryKindMap[i].kind; | 
| 505   } | 351   } | 
| 506   DVLOG(1) << "Unknown entry type for term " << term << ", type " << type; | 352   DVLOG(1) << "Unknown entry type for term " << term << ", type " << type; | 
| 507   return DocumentEntry::UNKNOWN; | 353   return DocumentEntry::UNKNOWN; | 
| 508 } | 354 } | 
| 509 | 355 | 
| 510 void DocumentEntry::OnAddCategory(Category* category) { | 356 void DocumentEntry::FillRemainingFields() { | 
| 511   if (category->type() == Category::KIND) | 357   // Set |kind_| and |labels_| based on the |categories_| in the class. | 
| 512     kind_ = GetEntryKindFromTerm(category->term()); | 358   // JSONValueConverter does not have the ability to catch an element in a list | 
| 513   else if (category->type() == Category::LABEL) | 359   // based on a predicate.  Thus we need to iterate over |categories_| and | 
| 514     labels_.push_back(category->label()); | 360   // find the elements to set these fields as a post-process. | 
|  | 361   for (size_t i = 0; i < categories_.size(); ++i) { | 
|  | 362     const Category* category = categories_[i]; | 
|  | 363     if (category->type() == Category::KIND) | 
|  | 364       kind_ = GetEntryKindFromTerm(category->term()); | 
|  | 365     else if (category->type() == Category::LABEL) | 
|  | 366       labels_.push_back(category->label()); | 
|  | 367   } | 
| 515 } | 368 } | 
| 516 | 369 | 
| 517 |  | 
| 518 const char DocumentFeed::kETagField[] = "gd$etag"; |  | 
| 519 const char DocumentFeed::kStartIndexField[] = "openSearch$startIndex.$t"; | 370 const char DocumentFeed::kStartIndexField[] = "openSearch$startIndex.$t"; | 
| 520 const char DocumentFeed::kItemsPerPageField[] = | 371 const char DocumentFeed::kItemsPerPageField[] = | 
| 521     "openSearch$itemsPerPage.$t"; | 372     "openSearch$itemsPerPage.$t"; | 
| 522 const char DocumentFeed::kUpdatedField[] = "updated.$t"; |  | 
| 523 const char DocumentFeed::kTitleField[] = "title.$t"; | 373 const char DocumentFeed::kTitleField[] = "title.$t"; | 
| 524 const char DocumentFeed::kEntryField[] = "entry"; | 374 const char DocumentFeed::kEntryField[] = "entry"; | 
| 525 | 375 | 
| 526 DocumentFeed::DocumentFeed() : start_index_(0), items_per_page_(0) { | 376 DocumentFeed::DocumentFeed() : start_index_(0), items_per_page_(0) { | 
| 527 } | 377 } | 
| 528 | 378 | 
| 529 DocumentFeed::~DocumentFeed() { | 379 DocumentFeed::~DocumentFeed() { | 
| 530 } | 380 } | 
| 531 | 381 | 
| 532 bool DocumentFeed::Parse(const DictionaryValue* value_dict) { | 382 // static | 
| 533   if (!value_dict->GetString(kETagField, &etag_)) { | 383 void DocumentFeed::RegisterJSONConverter( | 
| 534     DVLOG(1) << "Feed with no etag!"; | 384     base::JSONValueConverter<DocumentFeed>* converter) { | 
| 535     return false; | 385   // inheritance | 
| 536   } | 386   GDataEntry::RegisterJSONConverter( | 
| 537 | 387       reinterpret_cast<base::JSONValueConverter<GDataEntry>*>(converter)); | 
| 538   // TODO(zelidrag): Once we figure out where these will be used, we should | 388   // TODO(zelidrag): Once we figure out where these will be used, we should | 
| 539   // check for valid start_index_ and items_per_page_ values. | 389   // check for valid start_index_ and items_per_page_ values. | 
| 540   std::string start_index; | 390   converter->RegisterCustomField<int>( | 
| 541   if (!value_dict->GetString(kStartIndexField, &start_index) || | 391       kStartIndexField, &DocumentFeed::start_index_, &base::StringToInt); | 
| 542       !base::StringToInt(start_index, &start_index_)) { | 392   converter->RegisterCustomField<int>( | 
| 543     DVLOG(1) << "Feed with no startIndex! " << etag_; | 393       kItemsPerPageField, &DocumentFeed::items_per_page_, &base::StringToInt); | 
| 544     return false; | 394   converter->RegisterStringField(kTitleField, &DocumentFeed::title_); | 
| 545   } | 395   converter->RegisterRepeatedMessage(kEntryField, &DocumentFeed::entries_); | 
| 546 |  | 
| 547   std::string items_per_page; |  | 
| 548   if (!value_dict->GetString(kItemsPerPageField, &items_per_page) || |  | 
| 549       !base::StringToInt(items_per_page, &items_per_page_)) { |  | 
| 550     DVLOG(1) << "Feed with no itemsPerPage! " << etag_; |  | 
| 551     return false; |  | 
| 552   } |  | 
| 553 |  | 
| 554   if (!ParseDateTime(value_dict, kUpdatedField, &updated_time_)) { |  | 
| 555     DVLOG(1) << "Feed with no updated date! " << etag_; |  | 
| 556     return false; |  | 
| 557   } |  | 
| 558 |  | 
| 559   if (!value_dict->GetString(kTitleField, &title_)) { |  | 
| 560     DVLOG(1) << "Feed with no title!"; |  | 
| 561     return false; |  | 
| 562   } |  | 
| 563 |  | 
| 564   ListValue* entries = NULL; |  | 
| 565   if (value_dict->GetList(kEntryField, &entries)) { |  | 
| 566     for (ListValue::iterator iter = entries->begin(); |  | 
| 567          iter != entries->end(); |  | 
| 568          ++iter) { |  | 
| 569       DocumentEntry* entry = DocumentEntry::CreateFrom(*iter); |  | 
| 570       if (entry) |  | 
| 571         entries_.push_back(entry); |  | 
| 572     } |  | 
| 573   } |  | 
| 574 |  | 
| 575   // Parse categories. |  | 
| 576   if (!ParseCategories(value_dict)) |  | 
| 577     return false; |  | 
| 578 |  | 
| 579   // Parse author list. |  | 
| 580   if (!ParseAuthors(value_dict)) |  | 
| 581     return false; |  | 
| 582 |  | 
| 583   if (!ParseLinks(value_dict)) |  | 
| 584     return false; |  | 
| 585 |  | 
| 586   return true; |  | 
| 587 } | 396 } | 
| 588 | 397 | 
| 589 // static. | 398 // static | 
| 590 DocumentEntry* DocumentEntry::CreateFrom(base::Value* value) { | 399 DocumentEntry* DocumentEntry::CreateFrom(base::Value* value) { | 
| 591   if (!value || value->GetType() != Value::TYPE_DICTIONARY) | 400   base::JSONValueConverter<DocumentEntry> converter; | 
| 592     return NULL; |  | 
| 593 |  | 
| 594   DictionaryValue* root = reinterpret_cast<DictionaryValue*>(value); |  | 
| 595   scoped_ptr<DocumentEntry> entry(new DocumentEntry()); | 401   scoped_ptr<DocumentEntry> entry(new DocumentEntry()); | 
| 596   if (!entry->Parse(root)) { | 402   if (!converter.Convert(*value, entry.get())) { | 
| 597     DVLOG(1) << "Invalid document entry!"; | 403     DVLOG(1) << "Invalid document entry!"; | 
| 598     return NULL; | 404     return NULL; | 
| 599   } | 405   } | 
| 600 | 406 | 
|  | 407   entry->FillRemainingFields(); | 
| 601   return entry.release(); | 408   return entry.release(); | 
| 602 } | 409 } | 
| 603 | 410 | 
| 604 | 411 | 
| 605 // static. | 412 bool DocumentFeed::Parse(base::Value* value) { | 
|  | 413   base::JSONValueConverter<DocumentFeed> converter; | 
|  | 414   if (!converter.Convert(*value, this)) { | 
|  | 415     DVLOG(1) << "Invalid document feed!"; | 
|  | 416     return false; | 
|  | 417   } | 
|  | 418 | 
|  | 419   for (size_t i = 0; i < entries_.size(); ++i) { | 
|  | 420     entries_[i]->FillRemainingFields(); | 
|  | 421   } | 
|  | 422   return true; | 
|  | 423 } | 
|  | 424 | 
|  | 425 // static | 
| 606 DocumentFeed* DocumentFeed::CreateFrom(base::Value* value) { | 426 DocumentFeed* DocumentFeed::CreateFrom(base::Value* value) { | 
| 607   if (!value || value->GetType() != Value::TYPE_DICTIONARY) |  | 
| 608     return NULL; |  | 
| 609 |  | 
| 610   DictionaryValue* root_entry_dict = |  | 
| 611       reinterpret_cast<DictionaryValue*>(value); |  | 
| 612   scoped_ptr<DocumentFeed> feed(new DocumentFeed()); | 427   scoped_ptr<DocumentFeed> feed(new DocumentFeed()); | 
| 613   if (!feed->Parse(root_entry_dict)) { | 428   if (!feed->Parse(value)) { | 
| 614     DVLOG(1) << "Invalid document feed!"; | 429     DVLOG(1) << "Invalid document feed!"; | 
| 615     return NULL; | 430     return NULL; | 
| 616   } | 431   } | 
| 617 | 432 | 
| 618   return feed.release(); | 433   return feed.release(); | 
| 619 } | 434 } | 
| 620 | 435 | 
| 621 bool DocumentFeed::GetNextFeedURL(GURL* url) { | 436 bool DocumentFeed::GetNextFeedURL(GURL* url) { | 
| 622   DCHECK(url); | 437   DCHECK(url); | 
| 623   for (ScopedVector<Link>::iterator iter = links_.begin(); | 438   for (size_t i = 0; i < links_.size(); ++i) { | 
| 624       iter != links_.end(); ++iter) { | 439     if (links_[i]->type() == Link::NEXT) { | 
| 625     if ((*iter)->type() == Link::NEXT) { | 440       *url = links_[i]->href(); | 
| 626       *url = (*iter)->href(); |  | 
| 627       return true; | 441       return true; | 
| 628     } | 442     } | 
| 629   } | 443   } | 
| 630   return false; | 444   return false; | 
| 631 } | 445 } | 
| 632 | 446 | 
| 633 }  // namespace gdata | 447 }  // namespace gdata | 
| OLD | NEW | 
|---|