我在Haskell中编写了一个简单的XML解析器。 函数 convertXML 接收XML文件的内容,并返回进一步处理的提取值列表。
XML标记的一个属性还包含产品图像的URL,如果找到标记,我想扩展该功能以便下载它。
convertXML :: (Text.XML.Light.Lexer.XmlSource s) => s -> [String]
convertXML xml = productToCSV products
where
productToCSV [] = []
productToCSV (x:xs) = (getFields x) ++ (productToCSV
(elChildren x)) ++ (productToCSV xs)
getFields elm = case (qName . elName) elm of
"product" -> [attrField "uid", attrField "code"]
"name" -> [trim $ strContent elm]
"annotation" -> [trim $ strContent elm]
"text" -> [trim $ strContent elm]
"category" -> [attrField "uid", attrField "name"]
"manufacturer" -> [attrField "uid",
attrField "name"]
"file" -> [getImgName]
_ -> []
where
attrField fldName = trim . fromJust $
findAttr (unqual fldName) elm
getImgName = if (map toUpper $ attrField "type") == "FULL"
then
-- here I need some IO code
-- to download an image
-- fetchFile :: String -> IO String
attrField "file"
else []
products = findElements (unqual "product") productsTree
productsTree = fromJust $ findElement (unqual "products") xmlTree
xmlTree = fromJust $ parseXMLDoc xml
知道如何在 getImgName 函数中插入IO代码,还是必须将convertXML函数完全重写为不纯版本?
更新II convertXML函数的最终版本。 Carl建议的混合纯/不纯但干净的方式。返回对的第二个参数是一个IO动作,它运行图像下载并保存到磁盘并包装存储图像的本地路径列表。
convertXML :: (Text.XML.Light.Lexer.XmlSource s) => s -> ([String], IO [String])
convertXML xml = productToCSV products (return [])
where
productToCSV :: [Element] -> IO String -> ([String], IO [String])
productToCSV [] _ = ([], return [])
productToCSV (x:xs) (ys) = storeFields (getFields x)
( storeFields (productToCSV (elChildren x) (return []))
(productToCSV xs ys) )
getFields elm = case (qName . elName) elm of
"product" -> ([attrField "uid", attrField "code"], return [])
"name" -> ([trim $ strContent elm], return [])
"annotation" -> ([trim $ strContent elm], return [])
"text" -> ([trim $ strContent elm], return [])
"category" -> ([attrField "uid", attrField "name"], return [])
"manufacturer" -> ([attrField "uid",
attrField "name"], return [])
"file" -> getImg
_ -> ([], return [])
where
attrField fldName = trim . fromJust $
findAttr (unqual fldName) elm
getImg = if (map toUpper $ attrField "type") == "FULL"
then
( [attrField "file"], fetchFile url >>=
saveFile localPath >>
return [localPath] )
else ([], return [])
where
fName = attrField "file"
localPath = imagesDir ++ "/" ++ fName
url = attrField "folderUrl" ++ "/" ++ fName
storeFields (x1s, y1s) (x2s, y2s) = (x1s ++ x2s, liftM2 (++) y1s y2s)
products = findElements (unqual "product") productsTree
productsTree = fromJust $ findElement (unqual "products") xmlTree
xmlTree = fromJust $ parseXMLDoc xml
答案 0 :(得分:4)
更好的方法是让函数返回要作为结果的一部分下载的文件列表:
convertXML :: (Text.XML.Light.Lexer.XmlSource s) => s -> ([String], [URL])
并在单独的功能中下载它们。
答案 1 :(得分:3)
Haskell中类型系统的整个要点是除了IO操作之外你不能做IO - 类型IO a的值。有一些方法可以违反这一点,但由于与优化和懒惰评估的相互作用,它们存在完全不同于您所期望的行为风险。因此,除非您理解IO为何如此工作,否则不要试图使其工作方式不同。
但这种设计的一个非常重要的结果是IO动作是一流的。有点聪明,你可以编写你的函数:
convertXML :: (Text.XML.Light.Lexer.XmlSource s) => s -> ([String], IO [Image])
该对中的第二项将是一个IO动作,在执行时,将给出一个存在的图像列表。这样可以避免在convertXML之外使用图像加载代码,并且只有在您真正需要图像时才允许您执行IO。
答案 2 :(得分:2)
我基本上看到了方法:
我通常更喜欢第一种方法。 d