【问题标题】:F#, FParsec, and Calling a Stream Parser RecursivelyF#、FParsec 和递归调用流解析器
【发布时间】:2015-01-07 08:12:50
【问题描述】:

我正在开发一个使用 F# 和 FParsec 的多部分 MIME 解析器。我正在迭代开发,所以这是高度未精炼、脆弱的代码——它只解决了我的第一个直接问题。红色、绿色、重构。

我需要解析一个流而不是一个字符串,这真的让我陷入了循环。鉴于该约束,据我所知,我需要递归调用解析器。如何做到这一点超出了我的理解范围,至少就我目前为止的方式而言。

namespace MultipartMIMEParser

open FParsec
open System.IO

type private Post = { contentType : string
                    ; boundary    : string
                    ; subtype     : string
                    ; content     : string }

type MParser (s:Stream) =
  let ($) f x = f x
  let ascii = System.Text.Encoding.ASCII
  let str cs = System.String.Concat (cs:char list)
  let q = "\""
  let qP = pstring q
  let pSemicolon = pstring ";"
  let manyNoDoubleQuote = many $ noneOf q
  let enquoted = between qP qP manyNoDoubleQuote |>> str
  let skip = skipStringCI
  let pContentType = skip "content-type: "
                     >>. manyTill anyChar (attempt $ preturn () .>> pSemicolon)
                     |>> str
  let pBoundary = skip " boundary=" >>. enquoted
  let pSubtype = opt $ pSemicolon >>. skip " type=" >>. enquoted
  let pContent = many anyChar |>> str // TODO: The content parser needs to recurse on the stream.
  let pStream = pipe4 pContentType pBoundary pSubtype pContent
                      $ fun c b t s -> { contentType=c; boundary=b; subtype=t; content=s }
  let result s = match runParserOnStream pStream () "" s ascii with
                 | Success (r,_,_) -> r
                 | Failure (e,_,_) -> failwith (sprintf "%A" e)
  let r = result s
  member p.ContentType = r.contentType
  member p.Boundary = r.boundary
  member p.ContentSubtype = r.subtype
  member p.Content = r.content

示例 POST 的第一行如下:

content-type: Multipart/related; boundary="RN-Http-Body-Boundary"; type="multipart/related"

它跨越文件中的一行。内容中的其他子部分包括跨越多行的content-type 值,所以我知道如果我要重用它们,我必须改进我的解析器。

不知何故,我必须使用pBoundary 的(字符串?)结果调用pContent,以便我可以在适当的边界上拆分流的其余部分,然后以某种方式返回多个部分的内容帖子,每一个都是单独的帖子,带有标题和内容(显然必须是字符串以外的内容)。我的头在旋转。这段代码看起来太复杂了,无法解析一行。

非常感谢洞察力和智慧!

【问题讨论】:

    标签: f# multipart fparsec


    【解决方案1】:

    这是一个片段,可能会让你朝着正确的方向前进。

    让您的解析器吐出具有相同基本类型的内容。为此,我更喜欢使用 F# 的可区分联合。如果您确实需要将值推送到 Post 类型,请遍历返回的 AST 树。这就是我接近它的方式。

    #if INTERACTIVE
    #r"""..\..\FParsecCS.dll"""    // ... edit path as appropriate to bin/debug, etc.
    #r"""..\..\FParsec.dll"""
    #endif
    
    let packet = @"content-type: Multipart/related; boundary=""RN-Http-Body-Boundary""; type=""multipart/related""
    
    --RN-Http-Body-Boundary
    Message-ID: <25845033.1160080657073.JavaMail.webmethods@exshaw>
    Mime-Version: 1.0
    Content-Type: multipart/related; type=""application/xml"";
      boundary=""----=_Part_235_11184805.1160080657052""
    
    ------=_Part_235_11184805.1160080657052
    Content-Type: Application/XML
    Content-Transfer-Encoding: binary
    Content-Location: RN-Preamble
    Content-ID: <1430586.1160080657050.JavaMail.webmethods@exshaw>"
    
    //XML document begins here...
    
    type AST =
    | Document of AST list
    | Header of AST list
    /// ie. Content-Type is the tag, and it consists of a list of key value pairs
    | Tag of string * AST list  
    | KeyValue of string * string
    | Body of string
    

    上面的 AST DU 可以代表您在其他问题中发布的示例数据的第一遍。它可以比这更细,但通常越简单越好。我的意思是,您示例中的最终目标是 Post 类型,您可以通过一些简单的模式匹配来实现。

    【讨论】:

    • 我最终选择了一个有区别的工会,尽管我不确定我是否设计得很好。我根据这个反馈和来自my second question 的反馈询问了a new question。谢谢!
    猜你喜欢
    • 2015-01-09
    • 1970-01-01
    • 2022-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多