Благодаря ви за отговорите на първата ми публикация и втората ми публикация за този проект. Този въпрос по същество е същият въпрос като първия, но с моя код, актуализиран според обратната връзка, получена по тези два въпроса. Как да извикам моя анализатор рекурсивно?
Почесвам се по главата и се взирам безизразно в кода. Нямам идея накъде да отида от тук. Тогава се обръщам към stackoverflow.
Включих в коментарите на кода грешките по време на компилиране, които получавам. Един препъникамък може да бъде дискриминираният ми съюз. Не съм работил много с дискриминирани синдикати, така че може да използвам моя неправилно.
Примерният POST, с който работя, части от който включих в предишните си два въпроса, се състои от една граница, която включва втора публикация с нова граница. Този втори пост включва няколко допълнителни части, разделени от втората граница. Всяка от тези няколко допълнителни части е нова публикация, състояща се от заглавки и XML.
Моята цел в този проект е да изградя библиотека, която да се използва в нашето C# решение, като библиотеката приема поток и връща POST, анализиран на заглавки и части рекурсивно. Наистина искам F# да блесне тук.
namespace MultipartMIMEParser
open FParsec
open System.IO
type Header = { name : string
; value : string
; addl : (string * string) list option }
type Content = Content of string
| Post of Post list
and Post = { headers : Header list
; content : Content }
type UserState = { Boundary : string }
with static member Default = { Boundary="" }
module internal P =
let ($) f x = f x
let undefined = failwith "Undefined."
let ascii = System.Text.Encoding.ASCII
let str cs = System.String.Concat (cs:char list)
let makeHeader ((n,v),nvps) = { name=n; value=v; addl=nvps}
let runP p s = match runParserOnStream p UserState.Default "" s ascii with
| Success (r,_,_) -> r
| Failure (e,_,_) -> failwith (sprintf "%A" e)
let blankField = parray 2 newline
let delimited d e =
let pEnd = preturn () .>> e
let part = spaces
>>. (manyTill
$ noneOf d
$ (attempt (preturn () .>> pstring d)
<|> pEnd)) |>> str
in part .>>. part
let delimited3 firstDelimiter secondDelimiter thirdDelimiter endMarker =
delimited firstDelimiter endMarker
.>>. opt (many (delimited secondDelimiter endMarker
>>. delimited thirdDelimiter endMarker))
let isBoundary ((n:string),_) = n.ToLower() = "boundary"
let pHeader =
let includesBoundary (h:Header) = match h.addl with
| Some xs -> xs |> List.exists isBoundary
| None -> false
let setBoundary b = { Boundary=b }
in delimited3 ":" ";" "=" blankField
|>> makeHeader
>>= fun header stream -> if includesBoundary header
then
stream.UserState <- setBoundary (header.addl.Value
|> List.find isBoundary
|> snd)
Reply ()
else Reply ()
let pHeaders = manyTill pHeader $ attempt (preturn () .>> blankField)
let rec pContent (stream:CharStream<UserState>) =
match stream.UserState.Boundary with
| "" -> // Content is text.
let nl = System.Environment.NewLine
let unlines (ss:string list) = System.String.Join (nl,ss)
let line = restOfLine false
let lines = manyTill line $ attempt (preturn () .>> blankField)
in pipe2 pHeaders lines
$ fun h c -> { headers=h
; content=Content $ unlines c }
| _ -> // Content contains boundaries.
let b = "--" + stream.UserState.Boundary
// VS complains about pContent in the following line:
// Type mismatch. Expecting a
// Parser<'a,UserState>
// but given a
// CharStream<UserState> -> Parser<Post,UserState>
// The type 'Reply<'a>' does not match the type 'Parser<Post,UserState>'
let p = pipe2 pHeaders pContent $ fun h c -> { headers=h; content=c }
in skipString b
>>. manyTill p (attempt (preturn () .>> blankField))
// VS complains about Content.Post in the following line:
// Type mismatch. Expecting a
// Post list -> Post
// but given a
// Post list -> Content
// The type 'Post' does not match the type 'Content'
|>> Content.Post
// VS complains about pContent in the following line:
// Type mismatch. Expecting a
// Parser<'a,UserState>
// but given a
// CharStream<UserState> -> Parser<Post,UserState>
// The type 'Reply<'a>' does not match the type 'Parser<Post,UserState>'
let pStream = runP (pipe2 pHeaders pContent $ fun h c -> { headers=h; content=c })
type MParser (s:Stream) =
let r = P.pStream s
let findHeader name =
match r.headers |> List.tryFind (fun h -> h.name.ToLower() = name) with
| Some h -> h.value
| None -> ""
member p.Boundary =
let header = r.headers
|> List.tryFind (fun h -> match h.addl with
| Some xs -> xs |> List.exists P.isBoundary
| None -> false)
in match header with
| Some h -> h.addl.Value |> List.find P.isBoundary |> snd
| None -> ""
member p.ContentID = findHeader "content-id"
member p.ContentLocation = findHeader "content-location"
member p.ContentSubtype = findHeader "type"
member p.ContentTransferEncoding = findHeader "content-transfer-encoding"
member p.ContentType = findHeader "content-type"
member p.Content = r.content
member p.Headers = r.headers
member p.MessageID = findHeader "message-id"
member p.MimeVersion = findHeader "mime-version"
РЕДАКТИРАНЕ
В отговор на обратната връзка, която получих досега (благодаря!), Направих следните корекции, като получих анотираните грешки:
let rec pContent (stream:CharStream<UserState>) =
match stream.UserState.Boundary with
| "" -> // Content is text.
let nl = System.Environment.NewLine
let unlines (ss:string list) = System.String.Join (nl,ss)
let line = restOfLine false
let lines = manyTill line $ attempt (preturn () .>> blankField)
in pipe2 pHeaders lines
$ fun h c -> { headers=h
; content=Content $ unlines c }
| _ -> // Content contains boundaries.
let b = "--" + stream.UserState.Boundary
// The following complaint is about `pContent stream`:
// This expression was expected to have type
// Reply<'a>
// but here has type
// Parser<Post,UserState>
let p = pipe2 pHeaders (fun stream -> pContent stream) $ fun h c -> { headers=h; content=c }
in skipString b
>>. manyTill p (attempt (preturn () .>> blankField))
// VS complains about the line above:
// Type mismatch. Expecting a
// Parser<Post,UserState>
// but given a
// Parser<'a list,UserState>
// The type 'Post' does not match the type ''a list'
// See above complaint about `pContent stream`. Same complaint here.
let pStream = runP (pipe2 pHeaders (fun stream -> pContent stream) $ fun h c -> { headers=h; content=c })
Опитах да вмъкна Reply ()
s, но те просто върнаха анализатори, което означава, че c
по-горе стана Parser<...>
, а не Content
. Това изглежда беше крачка назад или поне в грешната посока. Все пак признавам невежеството си и приветствам корекцията!
pContent
като функция за анализатор, т.е. като функция, която връща стойностReply
, но вместо това връщате функции за анализатор и на двата клона. - person Stephan Tolksdorf   schedule 12.11.2014Reply ()
, ноc
след това промени типа отContent
наParser<...>
. Признавам невежеството си, но мисля, че това е грешна посока. Моля, поправете ме, ако греша. - person Jeff Maner   schedule 13.11.2014stream
наpContent
като аргумент на функциите за анализатор, които конструирате и в двата клона. В първия клон също трябва да обвиете стойносттаPost {...}
в списък и след това вContent.Post
. Можете бързо да видите това, като добавите изрична анотация за типа на връщания типpContent
. - person Stephan Tolksdorf   schedule 13.11.2014pContent
, може да бъде доста неефективно. Бих препоръчал да разделите анализатора на компоненти и след това да използватеcreateParserForwardedToRef
, за да разбиете директната рекурсия. Също така бих ви препоръчал да се опитате да разберете малко как функциите на анализатора и комбинаторите работят под капака (напр. като прочетете източника или ръководството за потребителя), което трябва да ви улесни при конструирането и отстраняването на грешки в анализаторите. - person Stephan Tolksdorf   schedule 13.11.2014