2月 062012
 

■参考
XMLPropertyListConfiguration (Commons Configuration 1.9-SNAPSHOT API)
http://commons.apache.org/configuration/apidocs/org/apache/commons/configuration/plist/XMLPropertyListConfiguration.html
—————————————————————
Property list file (plist) in XML format as used by Mac OS X (http://www.apple.com/DTDs/PropertyList-1.0.dtd).
This configuration doesn’t support the binary format used in OS X 10.4.
—————————————————————
  ↓↓↓↓↓↓
真・plist形式のxml読み込み用digester-rules(およびクラス) – terazzoの日記
http://d.hatena.ne.jp/terazzo/20080524/1211656594

■環境(STS)
JDK Compiler compliance level : 1.7
Groovy Compiler version 1.8.4.xx-20120118-1100-e37-M1
Maven Embedded (3.0.2/1.0.100.20110804-1717)
—————————————————————


  <dependencies>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-digester3</artifactId>
      <version>3.2</version>
    </dependency>
  </dependencies>

—————————————————————

■サンプルメインクラス(sample.Main.groovy)および実行結果


package sample

import parser.PlayList;
import parser.Track;
import parser.iTunesLibraryXMLParser;

def parser = new iTunesLibraryXMLParser("ライブラリ.xml")

PlayList playList = parser.getPlayList("ミュージック")
Map<Integer, Track> playlistItems = playList?.getPlaylistItems()

def artistList = []
for(Track track: playlistItems?.values()){
  artistList << track.getArtist()
}

def rankMap = [:]
artistList.each {
  rankMap.put(it, (rankMap.containsKey(it)?rankMap.get(it):0) +1)
}
rankMap.sort{it.value}.reverseEach {key, value ->
  println "$key : ${value}曲"
}

安室奈美恵 : 16曲
宇多田ヒカル : 15曲
RADWIMPS : 15曲
RIP SLYME : 14曲
矢井田瞳 : 13曲
YUKI : 12曲
福山雅治 : 10曲
大塚愛 : 9曲
いきものがかり : 9曲
絢香 : 8曲
ケツメイシ : 8曲
YUI : 7曲
Hilcrhyme : 7曲
 ・
 ・
 ・

■parser.utils.PropertyListUtils.groovy


package parser.utils

import java.io.IOException;
import java.net.URL;
import java.text.DateFormat
import java.text.ParseException;
import java.text.SimpleDateFormat;
import org.apache.commons.digester3.binder.DigesterLoader;
import org.apache.commons.digester3.Digester;
import org.apache.commons.digester3.xmlrules.FromXmlRulesModule;
import org.xml.sax.SAXException;

/**
 * plist-rule.xmlを使ったDigesterによってライブラリXMLを読み込むクラス
 */
public class PropertyListUtils  {
  /**
   * plist-rule.xmlから作成したRulesModule
   */
  class MyRulesModule extends FromXmlRulesModule {
    URL ruleXml = getClass().getClassLoader().getResource("parser/utils/plist-rule.xml");

    @Override
    protected void loadRules() {
      loadXMLRules(ruleXml);
    }
  }

  /**
   * ライブラリXMLファイル読み込み用のDigester
   */
  private static Digester digester =
  DigesterLoader.newLoader( new PropertyListUtils.MyRulesModule() ).newDigester();

  /**
   * 指定したurlの内容をplistとして読み込み、内部の情報を戻す。
   * 内部のタグについて、arrayはjava.util.Listに、dictはjava.util.Mapに、
   * string, integer, dateはそれぞれString, Integer, java.util.Dateに変換する。
   * @param url XML形式のplistファイルを示すURL
   * @return plistの中身を戻す
   */
  public static Map load(URL url) throws IOException, SAXException {
    return (Map)((PropertyList) digester.parse(url)).getContent();
  }
  /**
   * 指定したurlの内容をplistとして読み込み、内部の情報を戻す。
   * 内部のタグについて、arrayはjava.util.Listに、dictはjava.util.Mapに、
   * string, integer, dateはそれぞれString, Integer, java.util.Dateに変換する。
   * @param file XML形式のplistファイルを示すFile
   * @return plistの中身を戻す
   */
  public static Map load(File file) throws IOException, SAXException {
    return (Map)((PropertyList) digester.parse(file)).getContent();
  }
}

/**
 * digesterでタグを処理する際に一時的に値を保持するラッパーインタフェース
 * @author terazzo
 */
interface ValueWrapper {
  /** @return 内部に保持する値を戻す */
  Object getValue();
}

/**
 * ValueWrapperを受け取りValueWrapperの内部の値を取得するクラス
 * @author terazzo
 */
abstract class Peeler {
  /**
   * valueを追加する
   * @param value 追加する値
   */
  public abstract void add(Object value);
  /**
   * wrapperの代わりにwrapperが内部に保持する値を取り出して追加する
   * @param wrapper 追加する値を含むValueWrapper
   */
  public void add(ValueWrapper wrapper) {
    add(wrapper.getValue());
  }
}

/**
 * plistタグを読み込む為のラッパークラス
 * @author terazzo
 */
class PropertyList extends Peeler {
  /** 内部ではObjectで保持*/
  private Object content;
  /**
   * 値を設定する
   * @param value plistタグ内の値(Map/List/String/Integerなど)
   */
  public void add(Object value) {
    this.content = value;
  }
  /** @return 内部に保持する値を戻す */
  public Object getContent() {
    return this.content;
  }
}

/**
 * arrayタグを読み込む為のラッパークラス
 * @author terazzo
 */
class Array extends Peeler implements ValueWrapper {
  /** 内部ではListで保持*/
  private List list = new ArrayList();

  /**
   * valueをlistに追加する
   * @param value 追加する値
   */
  public void add(Object value) {
    this.list.add(value);
  }
  /** @return 内部に保持するList値を戻す */
  public Object getValue() {
    return this.list;
  }
}

/**
 * dateタグを読み込む為のラッパークラス
 */
class Date implements ValueWrapper {
  /** 日付フォーマット(ISO 8601。実際は"yyyy-MM-dd'T'HH:mm:ss'Z'"で固定)。 */
  private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";

  /** 内部的に保持する日付オブジェクト*/
  private java.util.Date date = null;

  /**
   * 文字列dateStringを日付として設定する
   * @param dateString 日付を表す文字列(フォーマットは"yyyy-MM-dd'T'HH:mm:ss'Z'")
   */
  public void takeDateString(String dateString) throws ParseException {
    DateFormat df = new SimpleDateFormat(DATE_FORMAT);
    df.setTimeZone(TimeZone.getTimeZone("UTC"));
    this.date = df.parse(dateString);
  }
  /** @return 内部に保持するDate値を戻す */
  public Object getValue() {
    return this.date;
  }
}

/**
 * dictタグを読み込む為のラッパークラス
 * @author terazzo
 */
class Dict extends Peeler implements ValueWrapper {
  /** 内部ではMapで保持*/
  private Map map = new HashMap();

  /** 最後に出現したkeyタグの値を保持 */
  private String key;

  /**
   * keyを設定する。この直後にaddした値をこのkeyを用いて内部のMapに追加する
   * @param key key文字列
   */
  public void setKey(String key) {
    this.key = key;
  }
  /**
   * valueをmapに追加する。最後に設定したkey値をキーとして使用する
   * @param value 追加する値
   */
  public void add(Object value) {
    this.map.put(this.key, value);
  }
  /** @return 内部に保持するMap値を戻す */
  public Object getValue() {
    return this.map;
  }
}

/**
* trueタグを読み込む為のラッパークラス
*/
class True implements ValueWrapper {
   /** @return 内部に保持するBoolean値を戻す */
   public Object getValue() {
     return Boolean.TRUE;
   }
}

/**
* falseタグを読み込む為のラッパークラス
*/
class False implements ValueWrapper {
   /** @return 内部に保持するBoolean値を戻す */
   public Object getValue() {
     return Boolean.FALSE;
   }
}

■parser.utils.plist-rule.xml


<?xml version = "1.0" encoding = "UTF-8" ?>
<!DOCTYPE digester-rules PUBLIC
  "-//Apache Commons //DTD digester-rules XML V1.0//EN"
  "http://commons.apache.org/digester/dtds/digester-rules-3.0.dtd">
  
<digester-rules>
    <object-create-rule pattern="plist" classname="parser.utils.PropertyList"/>
    <pattern value="*/key">
        <call-method-rule methodname="setKey" paramcount="1" paramtypes="java.lang.String"/>
        <call-param-rule paramnumber='0'/>
    </pattern>
    <pattern value="*/string">
        <call-method-rule methodname="add" paramcount="1" paramtypes="java.lang.String"/>
        <call-param-rule paramnumber='0'/>
    </pattern>
    <pattern value="*/integer">
        <call-method-rule methodname="add" paramcount="1" paramtypes="java.lang.Integer"/>
        <call-param-rule paramnumber='0'/>
    </pattern>
    <pattern value="*/true">
        <object-create-rule classname="parser.utils.True"/>
        <call-method-rule targetoffset="1" methodname="add" paramcount="1"
            paramtypes="parser.utils.ValueWrapper"/>
        <call-param-rule paramnumber='0'  from-stack="true"/>
    </pattern>
    <pattern value="*/false">
        <object-create-rule classname="parser.utils.False"/>
        <call-method-rule targetoffset="1" methodname="add" paramcount="1"
            paramtypes="parser.utils.ValueWrapper"/>
        <call-param-rule paramnumber='0'  from-stack="true"/>
    </pattern>
    <pattern value="*/date">
        <object-create-rule classname="parser.utils.Date"/>
        <call-method-rule targetoffset="1" methodname="add" paramcount="1"
            paramtypes="parser.utils.ValueWrapper"/>
        <call-param-rule paramnumber='0' from-stack="true"/>
        <call-method-rule targetoffset="0" methodname="takeDateString" paramcount="1" paramtypes="java.lang.String"/>
        <call-param-rule paramnumber='0'/>
    </pattern>
    <pattern value="*/dict">
        <object-create-rule classname="parser.utils.Dict"/>
        <call-method-rule targetoffset="1" methodname="add" paramcount="1" 
             paramtypes="parser.utils.ValueWrapper"/>
        <call-param-rule paramnumber='0'  from-stack="true"/>
    </pattern>
    <pattern value="*/array">
        <object-create-rule classname="parser.utils.Array"/>
        <call-method-rule targetoffset="1" methodname="add" paramcount="1" 
             paramtypes="parser.utils.ValueWrapper"/>
        <call-param-rule paramnumber='0'  from-stack="true"/>
    </pattern>
</digester-rules>

■parser.iTunesLibraryXMLParser.groovy


package parser

import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Map;

import org.xml.sax.SAXException;
import parser.utils.PropertyListUtils;

public class iTunesLibraryXMLParser {
  private static final String MAJOR_VERSION = "Major Version"
  private static final String MINOR_VERSION = "Minor Version"
  private static final String DATA = "Date"
  private static final String APPLICATION_VERSION = "Application Version"
  private static final String FEATURES = "Features"
  private static final String MUSIC_FOLDER = "Music Folder"
  private static final String LIBRARY_PERSISTENT_ID = "Library Persistent ID"
  private static final String TRACKS = "Tracks"
  private static final String PLAYLISTS = "Playlists"

  private Map content;

  iTunesLibraryXMLParser(String libraryXmlPath) throws IOException, SAXException{
    content = PropertyListUtils.load( new File(libraryXmlPath) );
    replaceTracks(content)
    replacePlaylists(content)
  }

  private def getIntegerValue = { Map map, String key -> (Integer) (map.get(key)) }
  private def getDateValue = { Map map, String key -> (Date) map.get(key) }
  private def getStringValue = { Map map, String key -> (String) map.get(key) }
  private def getBooleanValue = { Map map, String key -> (Boolean) map.get(key) }

  Integer getMajorVersion() {
    return getIntegerValue(content, MAJOR_VERSION)
  }
  Integer getMinorVersion() {
    return getIntegerValue(content, MINOR_VERSION)
  }
  Date getDate() {
    return getDateValue(content, DATA)
  }
  String getApplicationVersion() {
    return getStringValue(content, APPLICATION_VERSION)
  }
  Integer getFeatures() {
    return getIntegerValue(content, FEATURES)
  }
  String getMusicFolder() {
    return getStringValue(content, MUSIC_FOLDER)
  }
  String getLibraryPersistentID() {
    return getStringValue(content, LIBRARY_PERSISTENT_ID)
  }

  Map<Integer, Track> getTracks() {
    return (Map<Integer, Track>)content.get(TRACKS)
  }
  Map<String, PlayList> getPlayLists() {
    return (Map<String, PlayList>)content.get(PLAYLISTS)
  }
  PlayList getPlayList(String name) {
    return (PlayList)getPlayLists().get(name)
  }

  private def replaceTracks(Map content) {
    Map<String, Map> defTracks = (Map<String, Map>)content.get(TRACKS)
    Map<Integer, Track> newTracks = new HashMap<Integer, Track>()

    for (Map track : defTracks.values()) {
      def trackObj = new Track(track)
      newTracks.put(trackObj.getTrackID(), trackObj)
    }
    content.put(TRACKS, newTracks)
  }

  private def replacePlaylists(Map content) {
    List<Map> defPlaylists = (ArrayList<Map>)content.get(PLAYLISTS)
    Map<String, PlayList> newPlayLists = new LinkedHashMap<String, PlayList>()
    Map<Integer, Track> tracks = (Map<Integer, Track>)content.get(TRACKS)

    for(Map playlist : defPlaylists) {
      def playlistObj = new PlayList(playlist, tracks)
      newPlayLists.put(playlistObj.getName(), playlistObj)
    }
    content.put(PLAYLISTS, newPlayLists)
  }
}

■parser.PlayList.groovy


package parser

class PlayList {
  PlayList() {
  }
  PlayList(Map playlist, Map<Integer, Track> tracks) {
    def getIntegerValue = { String key -> (Integer) (playlist?.get(key)) }
    def getDateValue = { String key -> (Date) playlist?.get(key) }
    def getStringValue = { String key -> (String) playlist?.get(key) }
    def getBooleanValue = { String key -> (Boolean) playlist?.get(key) }

    setName(getStringValue("Name"))
    setMaster(getBooleanValue("Master"))
    setPlaylistID(getIntegerValue("Playlist ID"))
    setPlaylistPersistentID(getStringValue("Playlist Persistent ID"))
    setDistinguishedKind(getIntegerValue("Distinguished Kind"))
    setMusic(getBooleanValue("Music"))
    setMovies(getBooleanValue("Movies"))
    setAudiobooks(getBooleanValue("Audiobooks"))
    setBooks(getBooleanValue("Books"))
    setPurchasedMusic(getBooleanValue("Purchased Music"))
    setVisible(getBooleanValue("Visible"))
    setAllItems(getBooleanValue("All Items"))
    List<Map> playlistItems = (ArrayList<Map>)playlist?.get("Playlist Items")
    for(Map playlistItem : playlistItems) {
      Integer trackID = (Integer) (playlistItem?.get("Track ID"))
      getPlaylistItems().put(trackID, tracks?.get(trackID))
    }
  }

  /** Name */
  String name
  /** Master */
  Boolean master
  /** Playlist ID */
  Integer playlistID
  /** Playlist Persistent ID */
  String playlistPersistentID
  /** Distinguished Kind */
  Integer distinguishedKind
  /** Music */
  Boolean music
  /** Movies */
  Boolean movies
  /** Audiobooks */
  Boolean audiobooks
  /** Books */
  Boolean books
  /** Purchased Music */
  Boolean purchasedMusic
  /** Visible */
  Boolean visible
  /** All Items */
  Boolean allItems
  /** Playlist Items <Track ID, Track Object> */
  Map<Integer, Track> playlistItems = new LinkedHashMap<Integer, Track>()
}

■parser.Track.groovy


package parser

class Track {
  Track(){
  }

  Track(Map track){
    def getIntegerValue = { String key -> (Integer) (track?.get(key)) }
    def getDateValue = { String key -> (Date) track?.get(key) }
    def getStringValue = { String key -> (String) track?.get(key) }
    def getBooleanValue = { String key -> (Boolean) track?.get(key) }

    setTrackID(getIntegerValue("Track ID"))
    setName(getStringValue("Name"))
    setArtist(getStringValue("Artist"))
    setComposer(getStringValue("Composer"))
    setAlbum(getStringValue("Album"))
    setAlbumArtist(getStringValue("Album Artist"))
    setGenre(getStringValue("Genre"))
    setKind(getStringValue("Kind"))
    setSize(getIntegerValue("Size"))
    setTotalTime(getIntegerValue("Total Time"))
    setDiscNumber(getIntegerValue("Disc Number"))
    setDiscCount(getIntegerValue("Disc Count"))
    setTrackNumber(getIntegerValue("Track Number"))
    setYear(getIntegerValue("Year"))
    setDateModified(getDateValue("Date Modified"))
    setDateAdded(getDateValue("Date Added"))
    setBitRate(getIntegerValue("Bit Rate"))
    setSampleRate(getIntegerValue("Sample Rate"))
    setComments(getStringValue("Comments"))
    setPlayCount(getIntegerValue("Play Count"))
    setPlayDate(getIntegerValue("Play Date"))
    setPlayDateUTC(getDateValue("Play Date UTC"))
    setReleaseDate(getDateValue("Release Date"))
    setSkipCount(getIntegerValue("Skip Count"))
    setSkipDate(getDateValue("Skip Date"))
    setArtworkCount(getIntegerValue("Artwork Count"))
    setSortComposer(getStringValue("Sort Composer"))
    setPersistentID(getStringValue("Persistent ID"))
    setDisabled(getBooleanValue("Disabled"))
    setTrackType(getStringValue("Track Type"))
    setiTunesU(getBooleanValue("iTunesU"))
    setUnplayed(getBooleanValue("Unplayed"))
    setHasVideo(getBooleanValue("Has Video"))
    setHD(getBooleanValue("HD"))
    setVideoWidth(getIntegerValue("Video Width"))
    setVideoHeight(getIntegerValue("Video Height"))
    setMovie(getBooleanValue("Movie"))
    setLocation(getStringValue("Location"))
    setFileFolderCount(getIntegerValue("File Folder Count"))
    setLibraryFolderCount(getIntegerValue("Library Folder Count"))
  }

  /** Track ID */
  Integer trackID
  /** Name */
  String name
  /** Artist */
  String artist
  /** Composer */
  String composer
  /** Album */
  String album
  /** Album Artist */
  String albumArtist
  /** Genre */
  String genre
  /** Kind */
  String kind
  /** Size */
  Integer size
  /** Total Time */
  Integer totalTime
  /** Disc Number */
  Integer discNumber
  /** Disc Count */
  Integer discCount
  /** Track Number */
  Integer trackNumber
  /** Year */
  Integer year
  /** Date Modified */
  Date dateModified
  /** Date Added */
  Date dateAdded
  /** Bit Rate */
  Integer bitRate
  /** Sample Rate */
  Integer sampleRate
  /** Comments */
  String comments
  /** Play Count */
  Integer playCount
  /** Play Date */
  Integer playDate
  /** Play Date UTC */
  Date playDateUTC
  /** Release Date */
  Date releaseDate
  /** Skip Count */
  Integer skipCount
  /** Skip Date */
  Date skipDate
  /** Artwork Count */
  Integer artworkCount
  /** Sort Composer */
  String sortComposer
  /** Persistent ID */
  String persistentID
  /** Disabled */
  Boolean disabled
  /** Track Type */
  String trackType
  /** iTunesU */
  Boolean iTunesU
  /** Unplayed */
  Boolean unplayed
  /** Has Video */
  Boolean hasVideo
  /** HD */
  Boolean HD
  /** Video Width */
  Integer videoWidth
  /** Video Height */
  Integer videoHeight
  /** Movie */
  Boolean movie
  /** Location */
  String location
  /** File Folder Count */
  Integer fileFolderCount
  /** Library Folder Count */
  Integer libraryFolderCount
}

Sorry, the comment form is closed at this time.