2014-11-10 4 views
1

F #과 FParsec을 사용하여 다중 부분 MIME 파서를 개발 중입니다. 저는 반복적으로 개발하고 있습니다. 그래서 이것은 매우 정제되지 않은 부서지기 쉬운 코드입니다 - 그것은 단지 나의 첫 번째 즉각적인 문제를 해결할뿐입니다. 빨강, 녹색, 리펙터.F #, FParsec 및 스트림 파서를 반복적으로 호출

문자열 대신 문자열을 구문 분석해야하는데 실제로 루프를 보내고 있습니다. 그 제약 조건을 감안할 때 최선을 다해 파서를 재귀 적으로 호출해야합니다. 그 일을하는 방법은 최소한 내가 지금까지 진행 한 방식대로, 나의 켄을 초월한 것이다.

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 값을 포함하므로 재사용 할 경우 파서를 수정해야합니다.

어떻게 든 pContent (문자열?) 결과는 pBoundary입니다. 나머지 스트림 부분을 적절한 경계에서 분리하여 포스트의 각 내용에 대해 여러 부분을 반환 할 수 있습니다. 그 중 헤더와 내용 (분명히 문자열이 아닌 다른 것이어야 함)이있는 별도의 게시물이됩니다. 내 머리가 돌고있다. 이 코드는 이미 너무 복잡해서 한 줄을 파싱하지 못했습니다.

통찰력과 지혜에 대한 감사!

답변

2

이것은 올바른 방향으로 나아갈 수있는 단편입니다.

파서가 같은 기본 유형으로 뭔가를 내뿜도록하십시오. 나는이 목적을 위해 F #의 차별화 된 노동 조합을 사용하는 것을 선호한다. 값을 포스트 유형으로 푸시 (push)해야한다면 반환 된 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: <[email protected]> 
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: <[email protected]>" 

//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 타입이며, 당신은 간단한 패턴 매칭으로 그것을 얻을 수 있다는 것을 의미합니다.

+0

나는 차별화 된 노동 조합을 만들었지 만 잘 설계되었는지는 잘 모르겠다. 나는이 피드백과 피드백을 바탕으로 [새로운 질문] (http://stackoverflow.com/questions/26891564/f-fparsec-and-calling-a-stream-parser-recursively-second-take)을 요청했다. [내 두 번째 질문] (http://stackoverflow.com/questions/26875192/f-fparsec-and-updating-userstate). 고맙습니다! –

관련 문제