Commit 61a36729 authored by krb@chromium.org's avatar krb@chromium.org

Created helper class for parsing Apple style XML dictionaries.

Converted iTunes and iPhotos parser to use it.
BUG=302745

Review URL: https://codereview.chromium.org/436293002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@287940 0039d316-1c4b-4281-b951-d872f2087c98
parent a8c63be2
...@@ -97,4 +97,64 @@ std::string ReadFileAsString(base::File file) { ...@@ -97,4 +97,64 @@ std::string ReadFileAsString(base::File file) {
return result; return result;
} }
XmlDictReader::XmlDictReader(XmlReader* reader) : reader_(reader) {}
XmlDictReader::~XmlDictReader() {}
bool XmlDictReader::Read() {
if (reader_->NodeName() != "dict")
return false;
int dict_content_depth = reader_->Depth() + 1;
// Advance past the dict node and into the body of the dictionary.
if (!reader_->Read())
return false;
while (reader_->Depth() >= dict_content_depth && ShouldLoop()) {
if (!iapps::SeekToNodeAtCurrentDepth(reader_, "key"))
break;
std::string found_key;
if (!reader_->ReadElementContent(&found_key))
break;
DCHECK_EQ(dict_content_depth, reader_->Depth());
if (!HandleKey(found_key))
break;
}
// Seek to the end of the dictionary. Bail on end or error.
while (reader_->Depth() >= dict_content_depth && reader_->Next()) {
}
return FinishedOk();
}
bool XmlDictReader::ShouldLoop() {
return true;
}
bool XmlDictReader::HandleKey(const std::string& key) {
if (Found(key))
return AllowRepeats();
if (HandleKeyImpl(key)) {
found_.insert(key);
return true;
}
return false;
}
bool XmlDictReader::AllowRepeats() {
return false;
}
bool XmlDictReader::FinishedOk() {
return true;
}
bool XmlDictReader::SkipToNext() {
return SkipToNextElement(reader_) && reader_->Next();
}
bool XmlDictReader::Found(const std::string& key) const {
return ContainsKey(found_, key);
}
} // namespace iapps } // namespace iapps
...@@ -5,9 +5,11 @@ ...@@ -5,9 +5,11 @@
#ifndef CHROME_UTILITY_MEDIA_GALLERIES_IAPPS_XML_UTILS_H_ #ifndef CHROME_UTILITY_MEDIA_GALLERIES_IAPPS_XML_UTILS_H_
#define CHROME_UTILITY_MEDIA_GALLERIES_IAPPS_XML_UTILS_H_ #define CHROME_UTILITY_MEDIA_GALLERIES_IAPPS_XML_UTILS_H_
#include <set>
#include <string> #include <string>
#include "base/files/file.h" #include "base/files/file.h"
#include "base/stl_util.h"
class XmlReader; class XmlReader;
...@@ -34,6 +36,54 @@ bool ReadInteger(XmlReader* reader, uint64* result); ...@@ -34,6 +36,54 @@ bool ReadInteger(XmlReader* reader, uint64* result);
// Read in the contents of the given library xml |file| and return as a string. // Read in the contents of the given library xml |file| and return as a string.
std::string ReadFileAsString(base::File file); std::string ReadFileAsString(base::File file);
// Contains the common code and main loop for reading the key/values
// of an XML dict. The derived class must implement |HandleKeyImpl()|
// which is called with each key, and may re-implement |ShouldLoop()|,
// |FinishedOk()| and/or |AllowRepeats()|.
class XmlDictReader {
public:
explicit XmlDictReader(XmlReader* reader);
virtual ~XmlDictReader();
// The main loop of this class. Reads all the keys in the
// current element and calls |HandleKey()| with each.
bool Read();
// Re-implemented by derived class if it should bail from the
// loop earlier, such as if it encountered all required fields.
virtual bool ShouldLoop();
// Called by |Read()| with each key. Calls derived |HandleKeyImpl()|.
bool HandleKey(const std::string& key);
virtual bool HandleKeyImpl(const std::string& key) = 0;
// Re-implemented by the derived class (to return true) if
// it should allow fields to be repeated, but skipped.
virtual bool AllowRepeats();
// Re-implemented by derived class if it should test for required
// fields instead of just returning true.
virtual bool FinishedOk();
// A convenience function for the derived classes.
// Skips to next element.
bool SkipToNext();
// A convenience function for the derived classes.
// Used to test if all required keys have been encountered.
bool Found(const std::string& key) const;
protected:
XmlReader* reader_;
private:
// The keys that the reader has run into in this element.
std::set<std::string> found_;
DISALLOW_COPY_AND_ASSIGN(XmlDictReader);
};
} // namespace iapps } // namespace iapps
#endif // CHROME_UTILITY_MEDIA_GALLERIES_IAPPS_XML_UTILS_H_ #endif // CHROME_UTILITY_MEDIA_GALLERIES_IAPPS_XML_UTILS_H_
...@@ -29,55 +29,35 @@ struct AlbumInfo { ...@@ -29,55 +29,35 @@ struct AlbumInfo {
uint64 id; uint64 id;
}; };
// Walk through a dictionary filling in |result| with photo information. Return class PhotosXmlDictReader : public iapps::XmlDictReader {
// true if at least the id and location were found. public:
// In either case, the cursor is advanced out of the dictionary. PhotosXmlDictReader(XmlReader* reader, PhotoInfo* photo_info)
bool GetPhotoInfoFromDict(XmlReader* reader, PhotoInfo* result) { : iapps::XmlDictReader(reader), photo_info_(photo_info) {}
DCHECK(result);
if (reader->NodeName() != "dict")
return false;
int dict_content_depth = reader->Depth() + 1;
// Advance past the dict node and into the body of the dictionary.
if (!reader->Read())
return false;
bool found_location = false;
while (reader->Depth() >= dict_content_depth) {
if (!iapps::SeekToNodeAtCurrentDepth(reader, "key"))
break;
std::string found_key;
if (!reader->ReadElementContent(&found_key))
break;
DCHECK_EQ(dict_content_depth, reader->Depth());
if (found_key == "ImagePath") { virtual bool HandleKeyImpl(const std::string& key) OVERRIDE {
if (found_location) if (key == "ImagePath") {
break;
std::string value; std::string value;
if (!iapps::ReadString(reader, &value)) if (!iapps::ReadString(reader_, &value))
break; return false;
result->location = base::FilePath(value); photo_info_->location = base::FilePath(value);
found_location = true; } else if (key == "OriginalPath") {
} else if (found_key == "OriginalPath") {
std::string value; std::string value;
if (!iapps::ReadString(reader, &value)) if (!iapps::ReadString(reader_, &value))
break; return false;
result->original_location = base::FilePath(value); photo_info_->original_location = base::FilePath(value);
} else { } else if (!SkipToNext()) {
if (!iapps::SkipToNextElement(reader)) return false;
break;
if (!reader->Next())
break;
} }
return true;
} }
// Seek to the end of the dictionary virtual bool FinishedOk() OVERRIDE {
while (reader->Depth() >= dict_content_depth) return Found("ImagePath");
reader->Next(); }
return found_location; private:
} PhotoInfo* photo_info_;
};
// Contents of the album 'KeyList' key are // Contents of the album 'KeyList' key are
// <array> // <array>
...@@ -110,62 +90,40 @@ bool ReadStringArray(XmlReader* reader, std::set<uint64>* photo_ids) { ...@@ -110,62 +90,40 @@ bool ReadStringArray(XmlReader* reader, std::set<uint64>* photo_ids) {
return true; return true;
} }
bool GetAlbumInfoFromDict(XmlReader* reader, AlbumInfo* result) { class AlbumXmlDictReader : public iapps::XmlDictReader {
DCHECK(result); public:
if (reader->NodeName() != "dict") AlbumXmlDictReader(XmlReader* reader, AlbumInfo* album_info)
return false; : iapps::XmlDictReader(reader), album_info_(album_info) {}
int dict_content_depth = reader->Depth() + 1; virtual bool ShouldLoop() OVERRIDE {
// Advance past the dict node and into the body of the dictionary. return !(Found("AlbumId") && Found("AlbumName") && Found("KeyList"));
if (!reader->Read()) }
return false;
bool found_id = false; virtual bool HandleKeyImpl(const std::string& key) OVERRIDE {
bool found_name = false; if (key == "AlbumId") {
bool found_contents = false; if (!iapps::ReadInteger(reader_, &album_info_->id))
while (reader->Depth() >= dict_content_depth && return false;
!(found_id && found_name && found_contents)) { } else if (key == "AlbumName") {
if (!iapps::SeekToNodeAtCurrentDepth(reader, "key")) if (!iapps::ReadString(reader_, &album_info_->name))
break; return false;
std::string found_key; } else if (key == "KeyList") {
if (!reader->ReadElementContent(&found_key)) if (!iapps::SeekToNodeAtCurrentDepth(reader_, "array"))
break; return false;
DCHECK_EQ(dict_content_depth, reader->Depth()); if (!ReadStringArray(reader_, &album_info_->photo_ids))
return false;
if (found_key == "AlbumId") { } else if (!SkipToNext()) {
if (found_id) return false;
break;
if (!iapps::ReadInteger(reader, &result->id))
break;
found_id = true;
} else if (found_key == "AlbumName") {
if (found_name)
break;
if (!iapps::ReadString(reader, &result->name))
break;
found_name = true;
} else if (found_key == "KeyList") {
if (found_contents)
break;
if (!iapps::SeekToNodeAtCurrentDepth(reader, "array"))
break;
if (!ReadStringArray(reader, &result->photo_ids))
break;
found_contents = true;
} else {
if (!iapps::SkipToNextElement(reader))
break;
if (!reader->Next())
break;
} }
return true;
} }
// Seek to the end of the dictionary virtual bool FinishedOk() OVERRIDE {
while (reader->Depth() >= dict_content_depth) return !ShouldLoop();
reader->Next(); }
return found_id && found_name && found_contents; private:
} AlbumInfo* album_info_;
};
// Inside the master image list, we expect photos to be arranged as // Inside the master image list, we expect photos to be arranged as
// <dict> // <dict>
...@@ -213,7 +171,11 @@ bool ParseAllPhotos(XmlReader* reader, ...@@ -213,7 +171,11 @@ bool ParseAllPhotos(XmlReader* reader,
PhotoInfo photo_info; PhotoInfo photo_info;
photo_info.id = id; photo_info.id = id;
if (!GetPhotoInfoFromDict(reader, &photo_info)) { // 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; errors = true;
break; break;
} }
...@@ -231,72 +193,79 @@ bool ParseAllPhotos(XmlReader* reader, ...@@ -231,72 +193,79 @@ bool ParseAllPhotos(XmlReader* reader,
IPhotoLibraryParser::IPhotoLibraryParser() {} IPhotoLibraryParser::IPhotoLibraryParser() {}
IPhotoLibraryParser::~IPhotoLibraryParser() {} IPhotoLibraryParser::~IPhotoLibraryParser() {}
bool IPhotoLibraryParser::Parse(const std::string& library_xml) { class IPhotoLibraryXmlDictReader : public iapps::XmlDictReader {
XmlReader reader; public:
if (!reader.Load(library_xml)) IPhotoLibraryXmlDictReader(XmlReader* reader, parser::Library* library)
return false; : iapps::XmlDictReader(reader), library_(library), ok_(true) {}
// Find the plist node and then search within that tag. virtual bool ShouldLoop() OVERRIDE {
if (!iapps::SeekToNodeAtCurrentDepth(&reader, "plist")) return !(Found("List of Albums") && Found("Master Image List"));
return false; }
if (!reader.Read())
return false;
if (!iapps::SeekToNodeAtCurrentDepth(&reader, "dict"))
return false;
int dict_content_depth = reader.Depth() + 1;
// Advance past the dict node and into the body of the dictionary.
if (!reader.Read())
return false;
bool found_photos = false;
bool found_albums = false;
while (reader.Depth() >= dict_content_depth &&
!(found_photos && found_albums)) {
if (!iapps::SeekToNodeAtCurrentDepth(&reader, "key"))
break;
std::string found_key;
if (!reader.ReadElementContent(&found_key))
break;
DCHECK_EQ(dict_content_depth, reader.Depth());
if (found_key == "List of Albums") {
if (found_albums)
continue;
found_albums = true;
if (!iapps::SeekToNodeAtCurrentDepth(&reader, "array") || virtual bool HandleKeyImpl(const std::string& key) OVERRIDE {
!reader.Read()) { if (key == "List of Albums") {
continue; if (!iapps::SeekToNodeAtCurrentDepth(reader_, "array") ||
!reader_->Read()) {
return true;
} }
while (iapps::SeekToNodeAtCurrentDepth(reader_, "dict")) {
while (iapps::SeekToNodeAtCurrentDepth(&reader, "dict")) {
AlbumInfo album_info; AlbumInfo album_info;
if (GetAlbumInfoFromDict(&reader, &album_info)) { AlbumXmlDictReader dict_reader(reader_, &album_info);
if (dict_reader.Read()) {
parser::Album album; parser::Album album;
album = album_info.photo_ids; album = album_info.photo_ids;
// Strip / from album name and dedupe any collisions. // Strip / from album name and dedupe any collisions.
std::string name; std::string name;
base::ReplaceChars(album_info.name, "//", " ", &name); base::ReplaceChars(album_info.name, "//", " ", &name);
if (!ContainsKey(library_.albums, name)) { if (ContainsKey(library_->albums, name))
library_.albums[name] = album; name = name + "("+base::Uint64ToString(album_info.id)+")";
} else { library_->albums[name] = album;
library_.albums[name+"("+base::Uint64ToString(album_info.id)+")"] =
album;
}
} }
} }
} else if (found_key == "Master Image List") { } else if (key == "Master Image List") {
if (found_photos) if (!ParseAllPhotos(reader_, &library_->all_photos)) {
continue; ok_ = false;
found_photos = true;
if (!ParseAllPhotos(&reader, &library_.all_photos))
return false; return false;
}
} }
return true;
} }
return true; virtual bool FinishedOk() OVERRIDE {
return ok_;
}
// The IPhotoLibrary allows duplicate "List of Albums" and
// "Master Image List" keys (although that seems odd.)
virtual 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 } // namespace iphoto
...@@ -27,48 +27,26 @@ struct TrackInfo { ...@@ -27,48 +27,26 @@ struct TrackInfo {
std::string album; std::string album;
}; };
// Walk through a dictionary filling in |result| with track information. Return class TrackInfoXmlDictReader : public iapps::XmlDictReader {
// true if at least the id and location where found (artist and album may be public:
// empty). In either case, the cursor is advanced out of the dictionary. TrackInfoXmlDictReader(XmlReader* reader, TrackInfo* track_info) :
bool GetTrackInfoFromDict(XmlReader* reader, TrackInfo* result) { iapps::XmlDictReader(reader), track_info_(track_info) {}
DCHECK(result);
if (reader->NodeName() != "dict") virtual bool ShouldLoop() OVERRIDE {
return false; return !(Found("Track ID") && Found("Location") &&
Found("Album Artist") && Found("Album"));
int dict_content_depth = reader->Depth() + 1; }
// Advance past the dict node and into the body of the dictionary.
if (!reader->Read())
return false;
bool found_id = false; virtual bool HandleKeyImpl(const std::string& key) OVERRIDE {
bool found_location = false; if (key == "Track ID") {
bool found_artist = false; return iapps::ReadInteger(reader_, &track_info_->id);
bool found_album_artist = false; } else if (key == "Location") {
bool found_album = false;
while (reader->Depth() >= dict_content_depth &&
!(found_id && found_location && found_album_artist && found_album)) {
if (!iapps::SeekToNodeAtCurrentDepth(reader, "key"))
break;
std::string found_key;
if (!reader->ReadElementContent(&found_key))
break;
DCHECK_EQ(dict_content_depth, reader->Depth());
if (found_key == "Track ID") {
if (found_id)
break;
if (!iapps::ReadInteger(reader, &result->id))
break;
found_id = true;
} else if (found_key == "Location") {
if (found_location)
break;
std::string value; std::string value;
if (!iapps::ReadString(reader, &value)) if (!iapps::ReadString(reader_, &value))
break; return false;
GURL url(value); GURL url(value);
if (!url.SchemeIsFile()) if (!url.SchemeIsFile())
break; return false;
url::RawCanonOutputW<1024> decoded_location; url::RawCanonOutputW<1024> decoded_location;
url::DecodeURLEscapeSequences(url.path().c_str() + 1, // Strip /. url::DecodeURLEscapeSequences(url.path().c_str() + 1, // Strip /.
url.path().length() - 1, url.path().length() - 1,
...@@ -81,40 +59,37 @@ bool GetTrackInfoFromDict(XmlReader* reader, TrackInfo* result) { ...@@ -81,40 +59,37 @@ bool GetTrackInfoFromDict(XmlReader* reader, TrackInfo* result) {
decoded_location.length()); decoded_location.length());
std::string location = "/" + base::UTF16ToUTF8(location16); std::string location = "/" + base::UTF16ToUTF8(location16);
#endif #endif
result->location = base::FilePath(location); track_info_->location = base::FilePath(location);
found_location = true; } else if (key == "Artist") {
} else if (found_key == "Artist") { if (Found("Album Artist"))
if (found_artist || found_album_artist) return false;
break; return iapps::ReadString(reader_, &track_info_->artist);
if (!iapps::ReadString(reader, &result->artist)) } else if (key == "Album Artist") {
break; track_info_->artist.clear();
found_artist = true; return iapps::ReadString(reader_, &track_info_->artist);
} else if (found_key == "Album Artist") { } else if (key == "Album") {
if (found_album_artist) return iapps::ReadString(reader_, &track_info_->album);
break; } else if (!SkipToNext()) {
result->artist.clear(); return false;
if (!iapps::ReadString(reader, &result->artist))
break;
found_album_artist = true;
} else if (found_key == "Album") {
if (found_album)
break;
if (!iapps::ReadString(reader, &result->album))
break;
found_album = true;
} else {
if (!iapps::SkipToNextElement(reader))
break;
if (!reader->Next())
break;
} }
return true;
} }
// Seek to the end of the dictionary virtual bool FinishedOk() OVERRIDE {
while (reader->Depth() >= dict_content_depth) return Found("Track ID") && Found("Location");
reader->Next(); }
return found_id && found_location; private:
TrackInfo* track_info_;
};
// Walk through a dictionary filling in |result| with track information. Return
// true if at least the id and location where found (artist and album may be
// empty). In either case, the cursor is advanced out of the dictionary.
bool GetTrackInfoFromDict(XmlReader* reader, TrackInfo* result) {
DCHECK(result);
TrackInfoXmlDictReader dict_reader(reader, result);
return dict_reader.Read();
} }
} // namespace } // namespace
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment