Подтвердить что ты не робот

С Haskell, как обрабатывать большие объемы XML?

Я изучал дампы данных и до сих пор использовал дружественный XML и "синтаксический разбор" с регулярными выражениями. Мои попытки с использованием различных XML-библиотек Haskell для поиска первого сообщения в документе-порядке определенным пользователем все натолкнулись на неприятное изнашивание.

TagSoup

import Control.Monad
import Text.HTML.TagSoup

userid = "83805"

main = do
  posts <- liftM parseTags (readFile "posts.xml")
  print $ head $ map (fromAttrib "Id") $
                 filter (~== ("<row OwnerUserId=" ++ userid ++ ">"))
                 posts

hxt

import Text.XML.HXT.Arrow
import Text.XML.HXT.XPath

userid = "83805"

main = do
  runX $ readDoc "posts.xml" >>> posts >>> arr head
  where
    readDoc = readDocument [ (a_tagsoup, v_1)
                           , (a_parse_xml, v_1)
                           , (a_remove_whitespace, v_1)
                           , (a_issue_warnings, v_0)
                           , (a_trace, v_1)
                           ]

posts :: ArrowXml a => a XmlTree String
posts = getXPathTrees byUserId >>>
        getAttrValue "Id"
  where byUserId = "/posts/row/@OwnerUserId='" ++ userid ++ "'"

xml

import Control.Monad
import Control.Monad.Error
import Control.Monad.Trans.Maybe
import Data.Either
import Data.Maybe
import Text.XML.Light

userid = "83805"

main = do
  [posts,votes] <- forM ["posts", "votes"] $
    liftM parseXML . readFile . (++ ".xml")
  let ps = elemNamed "posts" posts
  putStrLn $ maybe "<not present>" show
           $ filterElement (byUser userid) ps

elemNamed :: String -> [Content] -> Element
elemNamed name = head . filter ((==name).qName.elName) . onlyElems

byUser :: String -> Element -> Bool
byUser id e = maybe False (==id) (findAttr creator e)
  where creator = QName "OwnerUserId" Nothing Nothing

Где я ошибся? Каков надлежащий способ обработки массивных XML-документов с помощью Haskell?

4b9b3361

Ответ 1

Я замечаю, что вы делаете String IO во всех этих случаях. Вы абсолютно должны использовать Data.Text или Data.Bytestring(.Lazy), если хотите обработать большие объемы текста эффективно, как String == [ Char], что является неприемлемым представлением для очень больших плоских файлов.

Это значит, что вам нужно использовать библиотеку XML Haskell, которая поддерживает байты. Несколько десятков библиотек xml находятся здесь: http://hackage.haskell.org/packages/archive/pkg-list.html#cat:xml

Я не уверен, какая поддержка вызывается, но это условие, которое вы ищете.

Ответ 2

Ниже приведен пример, который использует hexpat:

{-# LANGUAGE PatternGuards #-}

module Main where

import Text.XML.Expat.SAX

import qualified Data.ByteString.Lazy as B

userid = "83805"

main :: IO ()
main = B.readFile "posts.xml" >>= print . earliest
  where earliest :: B.ByteString -> SAXEvent String String
        earliest = head . filter (ownedBy userid) . parse opts
        opts = ParserOptions Nothing Nothing

ownedBy :: String -> SAXEvent String String -> Bool
ownedBy uid (StartElement "row" as)
  | Just ouid <- lookup "OwnerUserId" as = ouid == uid
  | otherwise = False
ownedBy _ _ = False

Определение ownedBy немного неуклюже. Возможно, вместо этого выглядит шаблон:

{-# LANGUAGE ViewPatterns #-}

module Main where

import Text.XML.Expat.SAX

import qualified Data.ByteString.Lazy as B

userid = "83805"

main :: IO ()
main = B.readFile "posts.xml" >>= print . earliest
  where earliest :: B.ByteString -> SAXEvent String String
        earliest = head . filter (ownedBy userid) . parse opts
        opts = ParserOptions Nothing Nothing

ownedBy :: String -> SAXEvent String String -> Bool
ownedBy uid (ownerUserId -> Just ouid) = uid == ouid
ownedBy _ _ = False

ownerUserId :: SAXEvent String String -> Maybe String
ownerUserId (StartElement "row" as) = lookup "OwnerUserId" as
ownerUserId _ = Nothing

Ответ 3

Вы можете попробовать мою библиотеку fast-tagsoup. Это простая замена tagsoup и анализ на скорости 20-200 МБ/с.

Проблема с пакетом tagoup заключается в том, что он работает со строкой внутри, даже если вы используете интерфейс Text или ByteString. fast-tagsoup работает со строгими ByteStrings, используя высокопроизводительный малый уровень синтаксического анализа, все еще возвращая ленивые теги в качестве вывода.

Ответ 4

TagSoup поддерживает ByteString через свой класс Text.StringLike. Единственные изменения, необходимые вашему примеру, заключались в вызове ByteString.Lazys readFile и добавлении fromString в fromAttrib:

import Text.StringLike
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Char8 as BSC

userid = "83805"
file = "blah//posts.xml"
main = do
posts <- liftM parseTags (BSL.readFile file)
print $ head $ map (fromAttrib (fromString "Id")) $
               filter (~== ("<row OwnerUserId=" ++ userid ++ ">"))
               posts  

Ваш пример работал у меня (4-гигабайтная оперативная память), занимая 6 минут; версия ByteString заняла 10 минут.

Ответ 5

У меня была аналогичная проблема (с использованием HXT). Я избегал проблемы с памятью, используя Expat parser with HXT. В XML файле размером 5 Мбайт, просто прочитав документ и распечатав его: потребление пиковой памяти перешло от 2Gigs до 180MB, а время выполнения было намного короче (не измерялось).

Ответ 6

Возможно, вам нужен ленивый синтаксический анализатор XML: ваше использование выглядит как довольно простое сканирование через вход. HaXml имеет ленивый парсер, хотя вы должны его явно запросить, импортировав правильный модуль.