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::RawCanonOutputW
<1024> decoded_location
;
73 url::DecodeURLEscapeSequences(url
.path().c_str() + 1, // Strip /.
74 url
.path().length() - 1,
77 base::string16
location(decoded_location
.data(),
78 decoded_location
.length());
80 base::string16
location16(decoded_location
.data(),
81 decoded_location
.length());
82 std::string location
= "/" + base::UTF16ToUTF8(location16
);
84 result
->location
= base::FilePath(location
);
85 found_location
= true;
86 } else if (found_key
== "Artist") {
87 if (found_artist
|| found_album_artist
)
89 if (!iapps::ReadString(reader
, &result
->artist
))
92 } else if (found_key
== "Album Artist") {
93 if (found_album_artist
)
95 result
->artist
.clear();
96 if (!iapps::ReadString(reader
, &result
->artist
))
98 found_album_artist
= true;
99 } else if (found_key
== "Album") {
102 if (!iapps::ReadString(reader
, &result
->album
))
106 if (!iapps::SkipToNextElement(reader
))
113 // Seek to the end of the dictionary
114 while (reader
->Depth() >= dict_content_depth
)
117 return found_id
&& found_location
;
122 ITunesLibraryParser::ITunesLibraryParser() {}
123 ITunesLibraryParser::~ITunesLibraryParser() {}
125 bool ITunesLibraryParser::Parse(const std::string
& library_xml
) {
128 if (!reader
.Load(library_xml
))
131 // Find the plist node and then search within that tag.
132 if (!iapps::SeekToNodeAtCurrentDepth(&reader
, "plist"))
137 if (!iapps::SeekToNodeAtCurrentDepth(&reader
, "dict"))
140 if (!iapps::SeekInDict(&reader
, "Tracks"))
143 // Once inside the Tracks dict, we expect track dictionaries keyed by id. i.e.
148 // <key>Track ID</key><integer>160</integer>
149 if (!iapps::SeekToNodeAtCurrentDepth(&reader
, "dict"))
151 int tracks_dict_depth
= reader
.Depth() + 1;
155 // Once parsing has gotten this far, return whatever is found, even if
156 // some of the data isn't extracted just right.
157 bool no_errors
= true;
158 bool track_found
= false;
159 while (reader
.Depth() >= tracks_dict_depth
) {
160 if (!iapps::SeekToNodeAtCurrentDepth(&reader
, "key"))
162 std::string key
; // Should match track id below.
163 if (!reader
.ReadElementContent(&key
))
166 bool id_valid
= base::StringToUint64(key
, &id
);
167 if (!reader
.SkipToElement())
170 TrackInfo track_info
;
171 if (GetTrackInfoFromDict(&reader
, &track_info
) &&
173 id
== track_info
.id
) {
174 if (track_info
.artist
.empty())
175 track_info
.artist
= "Unknown Artist";
176 if (track_info
.album
.empty())
177 track_info
.album
= "Unknown Album";
178 parser::Track
track(track_info
.id
, track_info
.location
);
179 library_
[track_info
.artist
][track_info
.album
].insert(track
);
186 return track_found
|| no_errors
;
189 } // namespace itunes