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.
5 #include "chrome/utility/media_galleries/itunes_library_parser.h"
9 #include "base/logging.h"
10 #include "base/strings/string16.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome/utility/media_galleries/iapps_xml_utils.h"
14 #include "third_party/libxml/chromium/libxml_utils.h"
16 #include "url/url_canon.h"
17 #include "url/url_util.h"
25 base::FilePath location
;
30 // Walk through a dictionary filling in |result| with track information. Return
31 // true if at least the id and location where found (artist and album may be
32 // empty). In either case, the cursor is advanced out of the dictionary.
33 bool GetTrackInfoFromDict(XmlReader
* reader
, TrackInfo
* result
) {
35 if (reader
->NodeName() != "dict")
38 int dict_content_depth
= reader
->Depth() + 1;
39 // Advance past the dict node and into the body of the dictionary.
43 bool found_id
= false;
44 bool found_location
= false;
45 bool found_artist
= false;
46 bool found_album_artist
= false;
47 bool found_album
= false;
48 while (reader
->Depth() >= dict_content_depth
&&
49 !(found_id
&& found_location
&& found_album_artist
&& found_album
)) {
50 if (!iapps::SeekToNodeAtCurrentDepth(reader
, "key"))
52 std::string found_key
;
53 if (!reader
->ReadElementContent(&found_key
))
55 DCHECK_EQ(dict_content_depth
, reader
->Depth());
57 if (found_key
== "Track ID") {
60 if (!iapps::ReadInteger(reader
, &result
->id
))
63 } else if (found_key
== "Location") {
67 if (!iapps::ReadString(reader
, &value
))
70 if (!url
.SchemeIsFile())
72 url_canon::RawCanonOutputW
<1024> decoded_location
;
73 url_util::DecodeURLEscapeSequences(url
.path().c_str() + 1, // Strip /.
74 url
.path().length() - 1,
77 string16
location(decoded_location
.data(), decoded_location
.length());
79 string16
location16(decoded_location
.data(), decoded_location
.length());
80 std::string location
= "/" + UTF16ToUTF8(location16
);
82 result
->location
= base::FilePath(location
);
83 found_location
= true;
84 } else if (found_key
== "Artist") {
85 if (found_artist
|| found_album_artist
)
87 if (!iapps::ReadString(reader
, &result
->artist
))
90 } else if (found_key
== "Album Artist") {
91 if (found_album_artist
)
93 result
->artist
.clear();
94 if (!iapps::ReadString(reader
, &result
->artist
))
96 found_album_artist
= true;
97 } else if (found_key
== "Album") {
100 if (!iapps::ReadString(reader
, &result
->album
))
104 if (!iapps::SkipToNextElement(reader
))
111 // Seek to the end of the dictionary
112 while (reader
->Depth() >= dict_content_depth
)
115 return found_id
&& found_location
;
120 ITunesLibraryParser::ITunesLibraryParser() {}
121 ITunesLibraryParser::~ITunesLibraryParser() {}
123 bool ITunesLibraryParser::Parse(const std::string
& library_xml
) {
126 if (!reader
.Load(library_xml
))
129 // Find the plist node and then search within that tag.
130 if (!iapps::SeekToNodeAtCurrentDepth(&reader
, "plist"))
135 if (!iapps::SeekToNodeAtCurrentDepth(&reader
, "dict"))
138 if (!iapps::SeekInDict(&reader
, "Tracks"))
141 // Once inside the Tracks dict, we expect track dictionaries keyed by id. i.e.
146 // <key>Track ID</key><integer>160</integer>
147 if (!iapps::SeekToNodeAtCurrentDepth(&reader
, "dict"))
149 int tracks_dict_depth
= reader
.Depth() + 1;
153 // Once parsing has gotten this far, return whatever is found, even if
154 // some of the data isn't extracted just right.
155 bool no_errors
= true;
156 bool track_found
= false;
157 while (reader
.Depth() >= tracks_dict_depth
) {
158 if (!iapps::SeekToNodeAtCurrentDepth(&reader
, "key"))
160 std::string key
; // Should match track id below.
161 if (!reader
.ReadElementContent(&key
))
164 bool id_valid
= base::StringToUint64(key
, &id
);
165 if (!reader
.SkipToElement())
168 TrackInfo track_info
;
169 if (GetTrackInfoFromDict(&reader
, &track_info
) &&
171 id
== track_info
.id
) {
172 if (track_info
.artist
.empty())
173 track_info
.artist
= "Unknown Artist";
174 if (track_info
.album
.empty())
175 track_info
.album
= "Unknown Album";
176 parser::Track
track(track_info
.id
, track_info
.location
);
177 library_
[track_info
.artist
][track_info
.album
].insert(track
);
184 return track_found
|| no_errors
;
187 } // namespace itunes