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

来源:互联网 发布:js调用浏览器下载功能 编辑:程序博客网 时间:2024/04/29 19:56

平常经常用千千静听来听音乐,长期以来就形成了一个自己最喜欢听的音乐列表。这几天想把这些音乐全部复制到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.Inputimport Text.XML.Light.Outputimport Text.XML.Light.Procimport Text.XML.Light.Typesimport Data.Maybeimport 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 之后再运行。






原创粉丝点击