// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/utility/media_galleries/iphoto_library_parser.h" #include #include "base/logging.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "chrome/utility/media_galleries/iapps_xml_utils.h" #include "third_party/libxml/chromium/libxml_utils.h" namespace iphoto { namespace { struct PhotoInfo { uint64 id; base::FilePath location; base::FilePath original_location; }; struct AlbumInfo { std::set photo_ids; std::string name; uint64 id; }; class PhotosXmlDictReader : public iapps::XmlDictReader { public: PhotosXmlDictReader(XmlReader* reader, PhotoInfo* photo_info) : iapps::XmlDictReader(reader), photo_info_(photo_info) {} bool HandleKeyImpl(const std::string& key) override { if (key == "ImagePath") { std::string value; if (!iapps::ReadString(reader_, &value)) return false; photo_info_->location = base::FilePath(value); } else if (key == "OriginalPath") { std::string value; if (!iapps::ReadString(reader_, &value)) return false; photo_info_->original_location = base::FilePath(value); } else if (!SkipToNext()) { return false; } return true; } bool FinishedOk() override { return Found("ImagePath"); } private: PhotoInfo* photo_info_; }; // Contents of the album 'KeyList' key are // // 1 // 2 // 3 // bool ReadStringArray(XmlReader* reader, std::set* photo_ids) { if (reader->NodeName() != "array") return false; // Advance past the array node and into the body of the array. if (!reader->Read()) return false; int array_content_depth = reader->Depth(); while (iapps::SeekToNodeAtCurrentDepth(reader, "string")) { if (reader->Depth() != array_content_depth) return false; std::string photo_id; if (!iapps::ReadString(reader, &photo_id)) continue; uint64 id; if (!base::StringToUint64(photo_id, &id)) continue; photo_ids->insert(id); } return true; } class AlbumXmlDictReader : public iapps::XmlDictReader { public: AlbumXmlDictReader(XmlReader* reader, AlbumInfo* album_info) : iapps::XmlDictReader(reader), album_info_(album_info) {} bool ShouldLoop() override { return !(Found("AlbumId") && Found("AlbumName") && Found("KeyList")); } bool HandleKeyImpl(const std::string& key) override { if (key == "AlbumId") { if (!iapps::ReadInteger(reader_, &album_info_->id)) return false; } else if (key == "AlbumName") { if (!iapps::ReadString(reader_, &album_info_->name)) return false; } else if (key == "KeyList") { if (!iapps::SeekToNodeAtCurrentDepth(reader_, "array")) return false; if (!ReadStringArray(reader_, &album_info_->photo_ids)) return false; } else if (!SkipToNext()) { return false; } return true; } bool FinishedOk() override { return !ShouldLoop(); } private: AlbumInfo* album_info_; }; // Inside the master image list, we expect photos to be arranged as // // $PHOTO_ID // // $photo properties // // $PHOTO_ID // // $photo properties // // ... // // Returns true on success, false on error. bool ParseAllPhotos(XmlReader* reader, std::set* all_photos) { if (!iapps::SeekToNodeAtCurrentDepth(reader, "dict")) return false; int photos_dict_depth = reader->Depth() + 1; if (!reader->Read()) return false; bool errors = false; while (reader->Depth() >= photos_dict_depth) { if (!iapps::SeekToNodeAtCurrentDepth(reader, "key")) break; std::string key; if (!reader->ReadElementContent(&key)) { errors = true; break; } uint64 id; bool id_valid = base::StringToUint64(key, &id); if (!id_valid || reader->Depth() != photos_dict_depth) { errors = true; break; } if (!iapps::SeekToNodeAtCurrentDepth(reader, "dict")) { errors = true; break; } PhotoInfo photo_info; photo_info.id = id; // Walk through a dictionary filling in |result| with photo information. // Return true if at least the location was found. // In either case, the cursor is advanced out of the dictionary. PhotosXmlDictReader dict_reader(reader, &photo_info); if (!dict_reader.Read()) { errors = true; break; } parser::Photo photo(photo_info.id, photo_info.location, photo_info.original_location); all_photos->insert(photo); } return !errors; } } // namespace IPhotoLibraryParser::IPhotoLibraryParser() {} IPhotoLibraryParser::~IPhotoLibraryParser() {} class IPhotoLibraryXmlDictReader : public iapps::XmlDictReader { public: IPhotoLibraryXmlDictReader(XmlReader* reader, parser::Library* library) : iapps::XmlDictReader(reader), library_(library), ok_(true) {} bool ShouldLoop() override { return !(Found("List of Albums") && Found("Master Image List")); } bool HandleKeyImpl(const std::string& key) override { if (key == "List of Albums") { if (!iapps::SeekToNodeAtCurrentDepth(reader_, "array") || !reader_->Read()) { return true; } while (iapps::SeekToNodeAtCurrentDepth(reader_, "dict")) { AlbumInfo album_info; AlbumXmlDictReader dict_reader(reader_, &album_info); if (dict_reader.Read()) { parser::Album album; album = album_info.photo_ids; // Strip / from album name and dedupe any collisions. std::string name; base::ReplaceChars(album_info.name, "//", " ", &name); if (ContainsKey(library_->albums, name)) name = name + "("+base::Uint64ToString(album_info.id)+")"; library_->albums[name] = album; } } } else if (key == "Master Image List") { if (!ParseAllPhotos(reader_, &library_->all_photos)) { ok_ = false; return false; } } return true; } bool FinishedOk() override { return ok_; } // The IPhotoLibrary allows duplicate "List of Albums" and // "Master Image List" keys (although that seems odd.) bool AllowRepeats() override { return true; } private: parser::Library* library_; // The base class bails when we request, and then calls |FinishedOk()| // to decide what to return. We need to remember that we bailed because // of an error. That's what |ok_| does. bool ok_; }; bool IPhotoLibraryParser::Parse(const std::string& library_xml) { XmlReader reader; if (!reader.Load(library_xml)) return false; // Find the plist node and then search within that tag. if (!iapps::SeekToNodeAtCurrentDepth(&reader, "plist")) return false; if (!reader.Read()) return false; if (!iapps::SeekToNodeAtCurrentDepth(&reader, "dict")) return false; IPhotoLibraryXmlDictReader dict_reader(&reader, &library_); return dict_reader.Read(); } } // namespace iphoto