杀鸡也用牛刀,Haskell 处理 XML 文档小试

平常经常用千千静听来听音乐,长期以来就形成了一个自己最喜欢听的音乐列表。这几天想把这些音乐全部复制到U盘,插在车上,开车的时候也可以听听。但是每个MP3、WMA分散在不同的文件夹,一个个复制的话工作量就大了。我的音乐列表一般存为千千静听的 *.ttpl 格式,其本质就是一个 XML 文件。一般格式如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<ttplaylist title="[默认]" version="4" generator="TTPlayer -- 5.9.6">
	<format tagtitle="%A - %T" deftitle="%F"/>
	<items count="163">
		<item file="E:\incomings\cd3\I Still Believe.mp3" title="I Still Believe" len="287111"/>
		<item file="E:\incomings\cd3\When You Believe.mp3" title="When You Believe" len="273136"/>
	</items>
</ttplaylist>

每个 item 节点中都有一个 file 属性,保存了 MP3 文件的具体位置。可以通过读取这个属性,找到 MP3 文件,再自动的把文件复制到 U 盘中。这个过程就是基本的读取 XML 节点属性、复制文件的操作,使用 Python 就可以轻松实现。但是最近学习了 Haskell,刚好可以用它来试试。


Haskell 中可以实现 XML操作的库有很多,比如 HaXml、HXT、xml 等。本文采用最简单的 xml 库。安装 xml 库时可以使用 Haskell 的安装工具 Cabal 来自动安装:cabal install xml。有一些音乐文件可能是中文的文件名,经过测试,Haskell 不支持 ANSI 形式的中文路径、文件名的文件操作。所以还需要调用 UTF8 的库:cabal install utf8-string。


首先,需要将 Prelude 中的 readFile 隐藏,使用 UTF8 的 readFile,这样子后面才能识别 ttpl 中的中文路径。

import Prelude hiding (readFile)
import System.IO.UTF8 (readFile)

然后,载入 xml 的相关库。

import Text.XML.Light.Input
import Text.XML.Light.Output
import Text.XML.Light.Proc
import Text.XML.Light.Types
import Data.Maybe
import Data.List

最后,载入复制文件的相关库。

import System.Directory

接下来,写一个解析 XML 文件的函数。

parse s = let contents = parseXML s
              quotes   = concatMap (findElements $ qqName "item") (onlyElems contents)
              symbols  = map (findAttr $ qqName "file") quotes
              files = map fromJust symbols
              qqName name = QName name Nothing Nothing
          in files

上面的 s 是从文件读取进来的文本内容。将这个文本使用 parseXML 解析,得到 content 的数组。使用 onlyElems 取所有的有效元素,交给 findElements 函数来找出所有节点名字为 item 的节点。然后将这些节点的 file 属性值取出来,构成一个数组作为 parse 函数的返回值。可见,使用 Haskell 这样的函数式编程语言来处理问题时,思维方式确实与命令式编程语言有着很大的不同。

接着,再写一个函数将找出来的所有文件复制到 U 盘中。

copyMP3 [] = do return ()        
copyMP3 (x:xs) = do
    let output = "U:\\" ++ findFilename x
    copyFile x output
    copyMP3 xs

copyMP3 函数将递归数组,将所有的文件复制到 U:\ 路径下。copyFile 是 System.Directory 中的一个函数,它的两个参数中如果含有中文,必须使用 UTF8 才能被正确识别,否则总是返回“找不到文件”的提示。因此,前面就需要引用 UTF8 的库。fileFilename 函数是从完整的路径(如:E:\incomings\cd3\I Still Believe.mp3)中提取出文件名(I Still Believe.mp3) 。函数定义如下:

findFilename s = drop (length s - (fromJust $ findIndex (=='\\') $ reverse s)) s

这里用到了 Data.List 中的多个方法,可能还会有更优雅的写法。最后就是在 main 函数中将所有功能组合起来。

main = do
    s <- readFile "r:\\test.ttpl"
    copyMP3 $ parse s
    putStrLn "ok"

最后,将所有代码保存到 CopyMP3.hs 文件中。可以使用 runhaskell CopyMP3.hs 来运行,或者使用 ghc -o CopyMP3.exe CopyMP3.hs 将它编译成 exe 之后再运行。






你可能感兴趣的:(杀鸡也用牛刀,Haskell 处理 XML 文档小试)