2011-05-09 3 views
2

У меня есть каталог с около 4500 XML (HTML5) файлами, и я хочу создать «манифест» их данных (по существу title и base/@href).Обработка (тоже) многих XML-файлов (с TagSoup)

С этой целью я использовал функцию для сбора всех соответствующих путей к файлу, открывая их с помощью readFile, отправив их в парсер, основанный на tagoup, и затем вывел/форматировал результирующий список.

Это работает для подмножества файлов, но в итоге работает в ошибке openFile: resource exhausted (Too many open files). После некоторого чтения это не так удивительно: я использую mapM parseMetaDataFile files, который сразу открывает все ручки.

Что я не могу понять, так это, как обойти проблему. Я пробовал прочитать немного об Итерате; Можно ли легко подключить это с помощью тегов? Строгий IO, так как я использовал его в любом случае (хе-хе), заморозил мой компьютер, хотя файлы не очень большие (в среднем 28 КБ).

Любые указатели были бы весьма благодарны. Я понимаю, что подход к созданию большого списка может также потерпеть неудачу, но 4.5k элементов не так уж и много ... Кроме того, должно быть меньше String и более ByteString везде.

Вот код. Я извиняюсь за наивность:

import System.FilePath 
import Text.HTML.TagSoup 

data MetaData = MetaData String String deriving (Show, Eq) 

-- | Given HTML input, produces a MetaData structure of its essentials. 
-- Should obviously account for errors, but simplified here. 
readMetaData :: String -> MetaData 
readMetaData input = MetaData title base 
where 
    title = 
    innerText $ 
    (takeWhile (~/= TagClose "title") . dropWhile (~/= TagOpen "title" [])) 
    tags 
    base = fromAttrib "href" $ head $ dropWhile (~/= TagOpen "base" []) tags 
    tags = parseTags input 

-- | Parses MetaData from a file. 
parseMetaDataFile :: FilePath -> IO MetaData 
parseMetaDataFile path = fmap readMetaData $ readFile path 

-- | From a given root, gets the FilePaths of the files we are interested in. 
-- Not implemented here. 
getHtmlFilePaths :: FilePath -> IO [FilePath] 
getHtmlFilePaths root = undefined 

main :: IO 
main = do 
    -- Will call openFile for every file, which gives too many open files. 
    metas <- mapM parseMetaDataFile =<< getHtmlFilePaths 

    -- Do stuff with metas, which will cause files to actually be read. 
+0

Вам необходимо подумать о своем дизайне, поскольку, видимо, существует так много файлов, что вы не можете одновременно открывать все свои ручки (ленивый подход), а также не открывать их и не читать их все одновременно (полностью строгий подход) , Итак, как насчет обработки их по одному файлу за раз, используя строгий IO (например, «Data.Text'). –

+0

Мне бы хотелось обработать их по одному файлу за раз! Я не знаю, как это сделать, хотя ... – vicvicvic

ответ

3

Быстрый и грязный раствор:

parseMetaDataFile path = withFile path $ \h -> do 
    [email protected](MetaData x y) <- fmap readMetaData $ hGetContents h 
    Control.Exception.evaluate (length (x ++ y)) 
    return res 

Немного лучше решение, чтобы написать правильную NFData экземпляр для MetaData, а не только с помощью оценки.

+0

А, это работает, потому что он * заставляет * x и y оцениваться, что заставляет содержимое файла получать? Раньше я пробовал 'withFile', но был укушен ленивой оценкой ... все (hGetContents затем назовут слишком поздно). – vicvicvic

+0

@vicvicvic: yep - он гарантирует, что вы действительно прочитали часть файла, который вам нужен, прежде чем закрыть дескриптор. – sclv

+1

Просто протестировал это решение, и оно работает так же, как я надеялся (~ 4 секунды, чтобы обработать все файлы). Возможно, это грязно, но так зацикливается на проблеме ввода-вывода на чистом языке, imho :) – vicvicvic

2

Если вы хотите сохранить текущий проект вы должны убедиться, что parseMetaDataFile потреблял всю строку из ReadFile перед возвращением. Когда readFile достигает конца файла, дескриптор файла будет закрыт.

+0

Есть ли очевидный способ сделать это? 'readMetaData' никогда не потребляет весь файл; Могу ли я «пропустить-поглотить» каким-то образом после того, как он сделал интересный материал? – vicvicvic

+0

@vicvicvic: см. Мой ответ. там файл просто закрыт ('byFile'), как только вы получите то, что хотите. – sclv