1

这是我正在解析的一些xml:

<?xml version="1.0" encoding="utf-8"?>
<data>
<row ows_Document='Weekly Report 10.21.2020'
     ows_Category='Weekly Report'/>
<row ows_Document='Daily Update 10.20.2020'
     ows_Category='Daily Update'/>
<row ows_Document='Weekly Report 10.14.2020'
     ows_Category='Weekly Report'/>
<row ows_Document='Weekly Report 10.07.2020'
     ows_Category='Weekly Report'/>
<row ows_Document='Spanish: Reporte Semanal 07.10.2020' 
     ows_Category='Weekly Report'/>
</data>

我一直在试图弄清楚如何让管道解析器拒绝记录,除非ows_Category它包含Weekly Report并且ows_Document不包含Spanish. 起初,我使用一个虚拟值(在parseDoc'下面)在解析后将它们过滤掉,但后来我意识到我应该能够使用Maybe(在下面其他相同的情况parseDoc下),以及用事件使用的层join折叠我的Maybetag'基于名称或属性匹配失败的解析器。它可以编译,但行为很奇怪,显然甚至没有尝试将某些元素发送到解析器!这怎么可能?

{-# LANGUAGE OverloadedStrings #-}

import           Conduit
import           Control.Monad
import qualified Data.ByteString.Lazy.Char8 as L8
import           Data.Foldable
import           Data.String
import qualified Data.Text                  as T
import           Data.XML.Types
import           Text.XML.Stream.Parse

newtype Doc = Doc
  { name :: String
  } deriving (Show)

main :: IO ()
main = do
  r <- L8.readFile "oha.xml"

  let doc = Doc . T.unpack
      check (x,y) a b = if y == "Weekly Report" && not (T.isInfixOf "Spanish" x) then a else b

      t :: (MonadThrow m, MonadIO m) => ((T.Text, T.Text) -> ConduitT Event o m c)
                                     -> ConduitT Event o m (Maybe c)
      t f = tag' "row" ((,) <$> requireAttr "ows_Document" <*> requireAttr "ows_Category") $ \x -> do
        liftIO $ print x
        f x

      parseDoc, parseDoc' :: (MonadThrow m, MonadIO m) => ConduitT Event o m (Maybe Doc)
      parseDoc  = (join <$>) . t $ \z@(x,_) -> return $       check z (Just $ doc x)  Nothing -- this version doesn't get sent all of the data! why!?!?
      parseDoc' =              t $ \z@(x,_) -> return $ doc $ check z             x $ T.pack bad -- dummy value

      parseDocs :: (MonadThrow m, MonadIO m) => ConduitT Event o m (Maybe Doc)
                                             -> ConduitT Event o m [Doc]
      parseDocs = f tagNoAttr "data" . many'
      f g n = force (n <> " required") . g (fromString n)

      go p = runConduit $ parseLBS def r .| parseDocs p
      bad = "no good"

  traverse_ print =<<                              go parseDoc
  putStrLn ""
  traverse_ print =<< filter ((/= bad) . name) <$> go parseDoc'

输出——注意parseDoc甚至没有发送一条记录(应该成功的记录,从 10.14 开始),而parseDoc'行为符合预期:

("Weekly Report 10.21.2020","Weekly Report")
("Daily Update 10.20.2020","Daily Update")
("Weekly Report 10.07.2020","Weekly Report")
("Spanish: Reporte Semanal 07.10.2020","Weekly Report")
Doc {name = "Weekly Report 10.21.2020"}
Doc {name = "Weekly Report 10.07.2020"}

("Weekly Report 10.21.2020","Weekly Report")
("Daily Update 10.20.2020","Daily Update")
("Weekly Report 10.14.2020","Weekly Report")
("Weekly Report 10.07.2020","Weekly Report")
("Spanish: Reporte Semanal 07.10.2020","Weekly Report")
Doc {name = "Weekly Report 10.21.2020"}
Doc {name = "Weekly Report 10.14.2020"}
Doc {name = "Weekly Report 10.07.2020"}

当我尝试通过删除与 相关的所有内容来进一步简化时ows_Category,突然parseDoc工作正常,确立了这个想法的合理性?当我改为删除与 相关的所有内容时ows_Document,问题仍然存在。

我怀疑我应该这样做requireAttrRaw,但我无法理解它并且找不到文档/示例。

这是否与Applicative- 现在我想起来了,它不应该基于检查价值观而失败,对吧?

更新

我从作者那里找到了该库先前版本的答案force "fail msg" $ return Nothing,其中包括在类似情况下的有趣之处,但它放弃了所有解析,而不是仅仅使当前解析失败。

这个评论表明我需要抛出一个异常,并且在源代码中,他们使用类似的东西lift $ throwM $ XmlException "failed check" $ Just event,但是就像这样force ... return Nothing,这会杀死所有的解析,而不仅仅是当前的解析器。我也不知道怎么弄到手event

这是一个合并的拉取请求,声称已经解决了这个问题,但它没有讨论如何使用它,只是它是“微不足道的”:)

回答

明确回答:

  parseAttributes :: AttrParser (T.Text, T.Text)
  parseAttributes = do
    d <- requireAttr "ows_Document"
    c <- requireAttr "ows_Category"
    ignoreAttrs
    guard $ not (T.isInfixOf "Spanish" d) && c == "Weekly Report"
    return d

  parseDoc :: (MonadThrow m, MonadIO m) => ConduitT Event o m (Maybe Doc)
  parseDoc = tag' "row" parseAttributes $ return . doc

或者,因为在这种情况下可以独立检查属性值:

  parseAttributes = requireAttrRaw' "ows_Document" (not . T.isInfixOf "Spanish")
                 <* requireAttrRaw' "ows_Category" ("Weekly Report" ==)
                 <* ignoreAttrs
    where requireAttrRaw' n f = requireAttrRaw ("required attr value failed condition: " <> n) $ \(n',as) ->
            asum $ (\(ContentText a) -> guard (n' == fromString n && f a) *> pure a) <$> as

但后者留下了以下问题requireAttrRaw

  • 如果我们负责验证,我们不应该知道命名空间Name吗?
  • 为什么要requireAttrRaw送我们[Content]而不是两个Maybe Content,一个用于ContentTextContentEntity
  • 我们应该如何处理ContentEntity“For pass-through parsing”?
4

1 回答 1

1

tl;drtag' "row" parseAttributes parseContent中,check函数属于parseAttributes,而不是parseContent


为什么它的行为不符合预期

xml-conduit(特别是)围绕以下不变量设计:

  1. 当解析器是 type 时ConduitT Event o m (Maybe a),该Maybe层编码Events 是否已被消费
  2. tag' parseName parseAttributes parseContentEvent当且仅当两者parseName和都parseAttributes成功时消耗s
  3. tag' parseName parseAttributes parseContentparseContent当且仅当两者都parseName成功parseAttributes时运行

parseDoc

  • check函数在parseContent部分中被调用;在这个阶段,tag'已经承诺消费Events,根据不变量 2
  • Maybe将 2层堆叠join在一起:
    • 函数的输出check,它编码当前<row/>元素是否相关
    • 来自签名的“标准”Maybe层,它根据不变量 1 对 stag'是否已被使用进行编码Event

这基本上打破了不变量 1:当check返回时Nothing,尽管消耗了整个元素的 s,但仍parseDoc 返回。这导致xml-conduit的所有组合子的未定义行为,特别是(分析如下。)NothingEvent<row/>many'


为什么它的行为方式

组合器many'依靠不变量 1 来完成它的工作。定义为many' consumer = manyIgnore consumer ignoreAnyTreeContent,即:

  1. 尝试consumer
  2. 如果consumer返回Nothing,则使用 跳过元素或内容ignoreAnyTreeContent假设它尚未被 消耗consumer,并递归回步骤(1)

在您的情况下,即使整个元素已被消耗,也会consumer返回Nothing该项目。因此,运行是作为跳过该特定 的一种方式,但实际上最终会跳过下一个 ( )。Daily Update 10.20.2020<row/>ignoreAnyTreeContent<row/>Weekly Report 10.14.2020


如何实现预期的行为

check逻辑移至parseAttributes部件,使Event消耗与是否check通过耦合。

于 2020-10-24T12:39:36.713 回答