@@ -136,14 +136,110 @@ impl ApeTag {
136136 . find ( |i| i. key ( ) . eq_ignore_ascii_case ( key) )
137137 }
138138
139- /// Insert an [`ApeItem`]
139+ fn get_mut ( & mut self , key : & str ) -> Option < & mut ApeItem > {
140+ self . items
141+ . iter_mut ( )
142+ . find ( |i| i. key ( ) . eq_ignore_ascii_case ( key) )
143+ }
144+
145+ /// Inserts an item
146+ ///
147+ /// This is the same as [`ApeTag::push()`], except it will remove any items with the same key.
148+ ///
149+ /// # Examples
140150 ///
141- /// This will remove any item with the same key prior to insertion
151+ /// ```rust
152+ /// use lofty::ape::{ApeItem, ApeTag};
153+ /// use lofty::tag::ItemValue;
154+ ///
155+ /// # fn main() -> lofty::error::Result<()> {
156+ /// let mut tag = ApeTag::default();
157+ /// tag.insert(ApeItem::new(
158+ /// String::from("Title"),
159+ /// ItemValue::Text(String::from("Title 1")),
160+ /// )?);
161+ /// tag.insert(ApeItem::new(
162+ /// String::from("Title"),
163+ /// ItemValue::Text(String::from("Title 2")),
164+ /// )?);
165+ ///
166+ /// // We only retain the last title inserted
167+ /// let mut titles = tag.get("Title").expect("should exist");
168+ /// let mut values = titles.text_values().expect("should be text");
169+ /// assert_eq!(values.next(), Some("Title 2"));
170+ /// assert!(values.next().is_none());
171+ /// # Ok(()) }
172+ /// ```
142173 pub fn insert ( & mut self , value : ApeItem ) {
143174 self . remove ( value. key ( ) ) ;
144175 self . items . push ( value) ;
145176 }
146177
178+ /// Appends an item
179+ ///
180+ /// If an item with the same key already exists, this will:
181+ ///
182+ /// * Append the value to the existing item if they are the same **text-based** [`ItemValue`] variant
183+ /// * Or replace the existing item if they are different [`ItemValue`]s or binary
184+ ///
185+ /// # Examples
186+ ///
187+ /// ```rust
188+ /// use lofty::ape::{ApeItem, ApeTag};
189+ /// use lofty::tag::ItemValue;
190+ ///
191+ /// # fn main() -> lofty::error::Result<()> {
192+ /// use lofty::tag::ItemValue;
193+ /// let mut tag = ApeTag::default();
194+ /// tag.push(ApeItem::new(
195+ /// String::from("Title"),
196+ /// ItemValue::Text(String::from("Title 1")),
197+ /// )?);
198+ /// tag.push(ApeItem::new(
199+ /// String::from("Title"),
200+ /// ItemValue::Text(String::from("Title 2")),
201+ /// )?);
202+ ///
203+ /// // We retain both titles
204+ /// {
205+ /// let mut titles = tag.get("Title").expect("should exist");
206+ /// let mut values = titles.text_values().expect("should be text");
207+ /// assert_eq!(values.next(), Some("Title 1"));
208+ /// assert_eq!(values.next(), Some("Title 2"));
209+ /// assert!(values.next().is_none());
210+ /// }
211+ ///
212+ /// // With different value types
213+ /// tag.push(ApeItem::new(
214+ /// String::from("Artist"),
215+ /// ItemValue::Text(String::from("Text artist")),
216+ /// )?);
217+ /// tag.push(ApeItem::new(
218+ /// String::from("Artist"),
219+ /// ItemValue::Binary(b"Binary artist".to_vec()),
220+ /// )?);
221+ ///
222+ /// let artist = tag.get("Artist").expect("should exist");
223+ /// assert!(matches!(artist.value(), ItemValue::Binary(_)));
224+ /// # Ok(()) }
225+ /// ```
226+ pub fn push ( & mut self , value : ApeItem ) {
227+ if let Some ( existing) = self . get_mut ( value. key ( ) ) {
228+ match ( & mut existing. value , & value. value ) {
229+ ( ItemValue :: Text ( existing_text) , ItemValue :: Text ( new_text) )
230+ | ( ItemValue :: Locator ( existing_text) , ItemValue :: Locator ( new_text) ) => {
231+ existing_text. push ( '\0' ) ;
232+ existing_text. push_str ( new_text) ;
233+ } ,
234+ _ => {
235+ let _ = std:: mem:: replace ( existing, value) ;
236+ } ,
237+ }
238+ return ;
239+ }
240+ self . items . push ( value) ;
241+ }
242+
147243 /// Remove an [`ApeItem`] by key
148244 ///
149245 /// NOTE: Like [`ApeTag::get`], this is not case-sensitive
@@ -193,7 +289,7 @@ impl ApeTag {
193289 } ,
194290 _ => {
195291 if let Ok ( item) = item. try_into ( ) {
196- self . insert ( item) ;
292+ self . push ( item) ;
197293 }
198294 } ,
199295 }
@@ -470,6 +566,23 @@ impl SplitTag for ApeTag {
470566 } ,
471567 ( k, _) => {
472568 let item = std:: mem:: replace ( item, ApeItem :: EMPTY ) ;
569+
570+ // Multi-value string
571+ if let Some ( text) = item. value . text ( ) {
572+ if text. contains ( '\0' ) {
573+ for value in text. split ( '\0' ) {
574+ let item_value = match & item. value {
575+ ItemValue :: Text ( _) => ItemValue :: Text ( value. to_string ( ) ) ,
576+ ItemValue :: Locator ( _) => ItemValue :: Locator ( value. to_string ( ) ) ,
577+ _ => unreachable ! ( ) ,
578+ } ;
579+
580+ tag. items . push ( TagItem :: new ( k, item_value) )
581+ }
582+ return false ; // Item consumed
583+ }
584+ }
585+
473586 tag. items . push ( TagItem :: new ( k, item. value ) ) ;
474587 false // Item consumed
475588 } ,
@@ -973,4 +1086,56 @@ mod tests {
9731086 assert ! ( ape. disk_total( ) . is_none( ) ) ;
9741087 assert_eq ! ( ape. track( ) , Some ( 2 ) ) ;
9751088 }
1089+
1090+ #[ test_log:: test]
1091+ fn split_text_values ( ) {
1092+ let mut ape = ApeTag :: new ( ) ;
1093+ ape. push (
1094+ ApeItem :: new (
1095+ String :: from ( "Artists" ) ,
1096+ ItemValue :: Text ( String :: from ( "Serial-ATA" ) ) ,
1097+ )
1098+ . unwrap ( ) ,
1099+ ) ;
1100+ ape. push (
1101+ ApeItem :: new (
1102+ String :: from ( "Artists" ) ,
1103+ ItemValue :: Text ( String :: from ( "Lofty" ) ) ,
1104+ )
1105+ . unwrap ( ) ,
1106+ ) ;
1107+
1108+ let artists = ape. get ( "Artists" ) . unwrap ( ) ;
1109+ assert_eq ! ( artists. value. text( ) , Some ( "Serial-ATA\0 Lofty" ) ) ;
1110+
1111+ let tag: Tag = ape. into ( ) ;
1112+ assert_eq ! ( tag. len( ) , 2 ) ;
1113+
1114+ let mut artists = tag. get_strings ( ItemKey :: TrackArtists ) ;
1115+ assert_eq ! ( artists. next( ) . unwrap( ) , "Serial-ATA" ) ;
1116+ assert_eq ! ( artists. next( ) . unwrap( ) , "Lofty" ) ;
1117+ assert ! ( artists. next( ) . is_none( ) ) ;
1118+ }
1119+
1120+ #[ test_log:: test]
1121+ fn merge_text_values ( ) {
1122+ let mut tag = Tag :: new ( TagType :: Ape ) ;
1123+ tag. push ( TagItem :: new (
1124+ ItemKey :: TrackArtists ,
1125+ ItemValue :: Text ( String :: from ( "Serial-ATA" ) ) ,
1126+ ) ) ;
1127+ tag. push ( TagItem :: new (
1128+ ItemKey :: TrackArtists ,
1129+ ItemValue :: Text ( String :: from ( "Lofty" ) ) ,
1130+ ) ) ;
1131+
1132+ let ape: ApeTag = tag. into ( ) ;
1133+ assert_eq ! ( ape. len( ) , 1 ) ;
1134+
1135+ let artists = ape. get ( "Artists" ) . unwrap ( ) ;
1136+ let mut values = artists. text_values ( ) . unwrap ( ) ;
1137+ assert_eq ! ( values. next( ) . unwrap( ) , "Serial-ATA" ) ;
1138+ assert_eq ! ( values. next( ) . unwrap( ) , "Lofty" ) ;
1139+ assert ! ( values. next( ) . is_none( ) ) ;
1140+ }
9761141}
0 commit comments