1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/utility/media_galleries/iphoto_library_parser.h" 6 7 #include <string> 8 9 #include "base/logging.h" 10 #include "base/stl_util.h" 11 #include "base/strings/string_number_conversions.h" 12 #include "base/strings/string_util.h" 13 #include "chrome/utility/media_galleries/iapps_xml_utils.h" 14 #include "third_party/libxml/chromium/libxml_utils.h" 15 16 namespace iphoto { 17 18 namespace { 19 20 struct PhotoInfo { 21 uint64 id; 22 base::FilePath location; 23 base::FilePath original_location; 24 }; 25 26 struct AlbumInfo { 27 std::set<uint64> photo_ids; 28 std::string name; 29 uint64 id; 30 }; 31 32 // Walk through a dictionary filling in |result| with photo information. Return 33 // true if at least the id and location were found. 34 // In either case, the cursor is advanced out of the dictionary. 35 bool GetPhotoInfoFromDict(XmlReader* reader, PhotoInfo* result) { 36 DCHECK(result); 37 if (reader->NodeName() != "dict") 38 return false; 39 40 int dict_content_depth = reader->Depth() + 1; 41 // Advance past the dict node and into the body of the dictionary. 42 if (!reader->Read()) 43 return false; 44 45 bool found_location = false; 46 while (reader->Depth() >= dict_content_depth) { 47 if (!iapps::SeekToNodeAtCurrentDepth(reader, "key")) 48 break; 49 std::string found_key; 50 if (!reader->ReadElementContent(&found_key)) 51 break; 52 DCHECK_EQ(dict_content_depth, reader->Depth()); 53 54 if (found_key == "ImagePath") { 55 if (found_location) 56 break; 57 std::string value; 58 if (!iapps::ReadString(reader, &value)) 59 break; 60 result->location = base::FilePath(value); 61 found_location = true; 62 } else if (found_key == "OriginalPath") { 63 std::string value; 64 if (!iapps::ReadString(reader, &value)) 65 break; 66 result->original_location = base::FilePath(value); 67 } else { 68 if (!iapps::SkipToNextElement(reader)) 69 break; 70 if (!reader->Next()) 71 break; 72 } 73 } 74 75 // Seek to the end of the dictionary 76 while (reader->Depth() >= dict_content_depth) 77 reader->Next(); 78 79 return found_location; 80 } 81 82 // Contents of the album 'KeyList' key are 83 // <array> 84 // <string>1</string> 85 // <string>2</string> 86 // <string>3</string> 87 // </array> 88 bool ReadStringArray(XmlReader* reader, std::set<uint64>* photo_ids) { 89 if (reader->NodeName() != "array") 90 return false; 91 92 // Advance past the array node and into the body of the array. 93 if (!reader->Read()) 94 return false; 95 96 int array_content_depth = reader->Depth(); 97 98 while (iapps::SeekToNodeAtCurrentDepth(reader, "string")) { 99 if (reader->Depth() != array_content_depth) 100 return false; 101 std::string photo_id; 102 if (!iapps::ReadString(reader, &photo_id)) 103 continue; 104 uint64 id; 105 if (!base::StringToUint64(photo_id, &id)) 106 continue; 107 photo_ids->insert(id); 108 } 109 110 return true; 111 } 112 113 bool GetAlbumInfoFromDict(XmlReader* reader, AlbumInfo* result) { 114 DCHECK(result); 115 if (reader->NodeName() != "dict") 116 return false; 117 118 int dict_content_depth = reader->Depth() + 1; 119 // Advance past the dict node and into the body of the dictionary. 120 if (!reader->Read()) 121 return false; 122 123 bool found_id = false; 124 bool found_name = false; 125 bool found_contents = false; 126 while (reader->Depth() >= dict_content_depth && 127 !(found_id && found_name && found_contents)) { 128 if (!iapps::SeekToNodeAtCurrentDepth(reader, "key")) 129 break; 130 std::string found_key; 131 if (!reader->ReadElementContent(&found_key)) 132 break; 133 DCHECK_EQ(dict_content_depth, reader->Depth()); 134 135 if (found_key == "AlbumId") { 136 if (found_id) 137 break; 138 if (!iapps::ReadInteger(reader, &result->id)) 139 break; 140 found_id = true; 141 } else if (found_key == "AlbumName") { 142 if (found_name) 143 break; 144 if (!iapps::ReadString(reader, &result->name)) 145 break; 146 found_name = true; 147 } else if (found_key == "KeyList") { 148 if (found_contents) 149 break; 150 if (!iapps::SeekToNodeAtCurrentDepth(reader, "array")) 151 break; 152 if (!ReadStringArray(reader, &result->photo_ids)) 153 break; 154 found_contents = true; 155 } else { 156 if (!iapps::SkipToNextElement(reader)) 157 break; 158 if (!reader->Next()) 159 break; 160 } 161 } 162 163 // Seek to the end of the dictionary 164 while (reader->Depth() >= dict_content_depth) 165 reader->Next(); 166 167 return found_id && found_name && found_contents; 168 } 169 170 // Inside the master image list, we expect photos to be arranged as 171 // <dict> 172 // <key>$PHOTO_ID</key> 173 // <dict> 174 // $photo properties 175 // </dict> 176 // <key>$PHOTO_ID</key> 177 // <dict> 178 // $photo properties 179 // </dict> 180 // ... 181 // </dict> 182 // Returns true on success, false on error. 183 bool ParseAllPhotos(XmlReader* reader, 184 std::set<iphoto::parser::Photo>* all_photos) { 185 if (!iapps::SeekToNodeAtCurrentDepth(reader, "dict")) 186 return false; 187 int photos_dict_depth = reader->Depth() + 1; 188 if (!reader->Read()) 189 return false; 190 191 bool errors = false; 192 while (reader->Depth() >= photos_dict_depth) { 193 if (!iapps::SeekToNodeAtCurrentDepth(reader, "key")) 194 break; 195 196 std::string key; 197 if (!reader->ReadElementContent(&key)) { 198 errors = true; 199 break; 200 } 201 uint64 id; 202 bool id_valid = base::StringToUint64(key, &id); 203 204 if (!id_valid || 205 reader->Depth() != photos_dict_depth) { 206 errors = true; 207 break; 208 } 209 if (!iapps::SeekToNodeAtCurrentDepth(reader, "dict")) { 210 errors = true; 211 break; 212 } 213 214 PhotoInfo photo_info; 215 photo_info.id = id; 216 if (!GetPhotoInfoFromDict(reader, &photo_info)) { 217 errors = true; 218 break; 219 } 220 221 parser::Photo photo(photo_info.id, photo_info.location, 222 photo_info.original_location); 223 all_photos->insert(photo); 224 } 225 226 return !errors; 227 } 228 229 } // namespace 230 231 IPhotoLibraryParser::IPhotoLibraryParser() {} 232 IPhotoLibraryParser::~IPhotoLibraryParser() {} 233 234 bool IPhotoLibraryParser::Parse(const std::string& library_xml) { 235 XmlReader reader; 236 if (!reader.Load(library_xml)) 237 return false; 238 239 // Find the plist node and then search within that tag. 240 if (!iapps::SeekToNodeAtCurrentDepth(&reader, "plist")) 241 return false; 242 if (!reader.Read()) 243 return false; 244 245 if (!iapps::SeekToNodeAtCurrentDepth(&reader, "dict")) 246 return false; 247 248 int dict_content_depth = reader.Depth() + 1; 249 // Advance past the dict node and into the body of the dictionary. 250 if (!reader.Read()) 251 return false; 252 253 bool found_photos = false; 254 bool found_albums = false; 255 while (reader.Depth() >= dict_content_depth && 256 !(found_photos && found_albums)) { 257 if (!iapps::SeekToNodeAtCurrentDepth(&reader, "key")) 258 break; 259 std::string found_key; 260 if (!reader.ReadElementContent(&found_key)) 261 break; 262 DCHECK_EQ(dict_content_depth, reader.Depth()); 263 264 if (found_key == "List of Albums") { 265 if (found_albums) 266 continue; 267 found_albums = true; 268 269 if (!iapps::SeekToNodeAtCurrentDepth(&reader, "array") || 270 !reader.Read()) { 271 continue; 272 } 273 274 while (iapps::SeekToNodeAtCurrentDepth(&reader, "dict")) { 275 AlbumInfo album_info; 276 if (GetAlbumInfoFromDict(&reader, &album_info)) { 277 parser::Album album; 278 album = album_info.photo_ids; 279 // Strip / from album name and dedupe any collisions. 280 std::string name; 281 base::ReplaceChars(album_info.name, "//", " ", &name); 282 if (!ContainsKey(library_.albums, name)) { 283 library_.albums[name] = album; 284 } else { 285 library_.albums[name+"("+base::Uint64ToString(album_info.id)+")"] = 286 album; 287 } 288 } 289 } 290 } else if (found_key == "Master Image List") { 291 if (found_photos) 292 continue; 293 found_photos = true; 294 if (!ParseAllPhotos(&reader, &library_.all_photos)) 295 return false; 296 } 297 } 298 299 return true; 300 } 301 302 } // namespace iphoto 303