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 class PhotosXmlDictReader : public iapps::XmlDictReader { 33 public: 34 PhotosXmlDictReader(XmlReader* reader, PhotoInfo* photo_info) 35 : iapps::XmlDictReader(reader), photo_info_(photo_info) {} 36 37 virtual bool HandleKeyImpl(const std::string& key) OVERRIDE { 38 if (key == "ImagePath") { 39 std::string value; 40 if (!iapps::ReadString(reader_, &value)) 41 return false; 42 photo_info_->location = base::FilePath(value); 43 } else if (key == "OriginalPath") { 44 std::string value; 45 if (!iapps::ReadString(reader_, &value)) 46 return false; 47 photo_info_->original_location = base::FilePath(value); 48 } else if (!SkipToNext()) { 49 return false; 50 } 51 return true; 52 } 53 54 virtual bool FinishedOk() OVERRIDE { 55 return Found("ImagePath"); 56 } 57 58 private: 59 PhotoInfo* photo_info_; 60 }; 61 62 // Contents of the album 'KeyList' key are 63 // <array> 64 // <string>1</string> 65 // <string>2</string> 66 // <string>3</string> 67 // </array> 68 bool ReadStringArray(XmlReader* reader, std::set<uint64>* photo_ids) { 69 if (reader->NodeName() != "array") 70 return false; 71 72 // Advance past the array node and into the body of the array. 73 if (!reader->Read()) 74 return false; 75 76 int array_content_depth = reader->Depth(); 77 78 while (iapps::SeekToNodeAtCurrentDepth(reader, "string")) { 79 if (reader->Depth() != array_content_depth) 80 return false; 81 std::string photo_id; 82 if (!iapps::ReadString(reader, &photo_id)) 83 continue; 84 uint64 id; 85 if (!base::StringToUint64(photo_id, &id)) 86 continue; 87 photo_ids->insert(id); 88 } 89 90 return true; 91 } 92 93 class AlbumXmlDictReader : public iapps::XmlDictReader { 94 public: 95 AlbumXmlDictReader(XmlReader* reader, AlbumInfo* album_info) 96 : iapps::XmlDictReader(reader), album_info_(album_info) {} 97 98 virtual bool ShouldLoop() OVERRIDE { 99 return !(Found("AlbumId") && Found("AlbumName") && Found("KeyList")); 100 } 101 102 virtual bool HandleKeyImpl(const std::string& key) OVERRIDE { 103 if (key == "AlbumId") { 104 if (!iapps::ReadInteger(reader_, &album_info_->id)) 105 return false; 106 } else if (key == "AlbumName") { 107 if (!iapps::ReadString(reader_, &album_info_->name)) 108 return false; 109 } else if (key == "KeyList") { 110 if (!iapps::SeekToNodeAtCurrentDepth(reader_, "array")) 111 return false; 112 if (!ReadStringArray(reader_, &album_info_->photo_ids)) 113 return false; 114 } else if (!SkipToNext()) { 115 return false; 116 } 117 return true; 118 } 119 120 virtual bool FinishedOk() OVERRIDE { 121 return !ShouldLoop(); 122 } 123 124 private: 125 AlbumInfo* album_info_; 126 }; 127 128 // Inside the master image list, we expect photos to be arranged as 129 // <dict> 130 // <key>$PHOTO_ID</key> 131 // <dict> 132 // $photo properties 133 // </dict> 134 // <key>$PHOTO_ID</key> 135 // <dict> 136 // $photo properties 137 // </dict> 138 // ... 139 // </dict> 140 // Returns true on success, false on error. 141 bool ParseAllPhotos(XmlReader* reader, 142 std::set<iphoto::parser::Photo>* all_photos) { 143 if (!iapps::SeekToNodeAtCurrentDepth(reader, "dict")) 144 return false; 145 int photos_dict_depth = reader->Depth() + 1; 146 if (!reader->Read()) 147 return false; 148 149 bool errors = false; 150 while (reader->Depth() >= photos_dict_depth) { 151 if (!iapps::SeekToNodeAtCurrentDepth(reader, "key")) 152 break; 153 154 std::string key; 155 if (!reader->ReadElementContent(&key)) { 156 errors = true; 157 break; 158 } 159 uint64 id; 160 bool id_valid = base::StringToUint64(key, &id); 161 162 if (!id_valid || 163 reader->Depth() != photos_dict_depth) { 164 errors = true; 165 break; 166 } 167 if (!iapps::SeekToNodeAtCurrentDepth(reader, "dict")) { 168 errors = true; 169 break; 170 } 171 172 PhotoInfo photo_info; 173 photo_info.id = id; 174 // Walk through a dictionary filling in |result| with photo information. 175 // Return true if at least the location was found. 176 // In either case, the cursor is advanced out of the dictionary. 177 PhotosXmlDictReader dict_reader(reader, &photo_info); 178 if (!dict_reader.Read()) { 179 errors = true; 180 break; 181 } 182 183 parser::Photo photo(photo_info.id, photo_info.location, 184 photo_info.original_location); 185 all_photos->insert(photo); 186 } 187 188 return !errors; 189 } 190 191 } // namespace 192 193 IPhotoLibraryParser::IPhotoLibraryParser() {} 194 IPhotoLibraryParser::~IPhotoLibraryParser() {} 195 196 class IPhotoLibraryXmlDictReader : public iapps::XmlDictReader { 197 public: 198 IPhotoLibraryXmlDictReader(XmlReader* reader, parser::Library* library) 199 : iapps::XmlDictReader(reader), library_(library), ok_(true) {} 200 201 virtual bool ShouldLoop() OVERRIDE { 202 return !(Found("List of Albums") && Found("Master Image List")); 203 } 204 205 virtual bool HandleKeyImpl(const std::string& key) OVERRIDE { 206 if (key == "List of Albums") { 207 if (!iapps::SeekToNodeAtCurrentDepth(reader_, "array") || 208 !reader_->Read()) { 209 return true; 210 } 211 while (iapps::SeekToNodeAtCurrentDepth(reader_, "dict")) { 212 AlbumInfo album_info; 213 AlbumXmlDictReader dict_reader(reader_, &album_info); 214 if (dict_reader.Read()) { 215 parser::Album album; 216 album = album_info.photo_ids; 217 // Strip / from album name and dedupe any collisions. 218 std::string name; 219 base::ReplaceChars(album_info.name, "//", " ", &name); 220 if (ContainsKey(library_->albums, name)) 221 name = name + "("+base::Uint64ToString(album_info.id)+")"; 222 library_->albums[name] = album; 223 } 224 } 225 } else if (key == "Master Image List") { 226 if (!ParseAllPhotos(reader_, &library_->all_photos)) { 227 ok_ = false; 228 return false; 229 } 230 } 231 return true; 232 } 233 234 virtual bool FinishedOk() OVERRIDE { 235 return ok_; 236 } 237 238 // The IPhotoLibrary allows duplicate "List of Albums" and 239 // "Master Image List" keys (although that seems odd.) 240 virtual bool AllowRepeats() OVERRIDE { 241 return true; 242 } 243 244 private: 245 parser::Library* library_; 246 247 // The base class bails when we request, and then calls |FinishedOk()| 248 // to decide what to return. We need to remember that we bailed because 249 // of an error. That's what |ok_| does. 250 bool ok_; 251 }; 252 253 bool IPhotoLibraryParser::Parse(const std::string& library_xml) { 254 XmlReader reader; 255 if (!reader.Load(library_xml)) 256 return false; 257 258 // Find the plist node and then search within that tag. 259 if (!iapps::SeekToNodeAtCurrentDepth(&reader, "plist")) 260 return false; 261 if (!reader.Read()) 262 return false; 263 264 if (!iapps::SeekToNodeAtCurrentDepth(&reader, "dict")) 265 return false; 266 267 IPhotoLibraryXmlDictReader dict_reader(&reader, &library_); 268 return dict_reader.Read(); 269 } 270 271 } // namespace iphoto 272