2

新库:XParsec

这个问题导致了 F# 3.0 中与流和类型无关、非线性、可扩展的解析秒实现——受 FParsec 启发,从字符和线性流中解放出来并简化了:http ://corsis.github.com/XParsec/


图案

1 = < font=?'Bold' bbox=F'l ..' s      >                       ;       < ~s >*
2 = < font=!'Bold' bbox=F'l ..' s=?'(' > | [ 1.l < 2.l ]       ;       < ~s >*
3 = < font=!'Bold' bbox=F'l ..' s=?')' > | [ 1.l < 3.l ]

在哪里

element names are left unspecified
font, bbox and s are attributes

V = string, N = string

?  :: V -> bool                -- value          contains string
!  :: V -> bool = not . (?)    -- value does not contain  string
~  :: N -> bool                -- value of attribute N is empty or whitespace
F  :: V -> [(N, float)]        -- extracts a list of named floats from value
RM :: V -> bool                -- value matches regular expression
[] :: [bool]                   -- list of conditions

代码

open System.Xml.Linq
open System.Collections.Generic

let inline (-?-)  a b = (a : string).Contains b
let inline (~~) s = s |> String.IsNullOrWhiteSpace
let inline (!>) x = ( ^a : (static member op_Implicit : ^b -> ^a) x )

let inline (@)  (x : XElement) n   = let a = x.Attribute(!> n) in if a <> null then a.Value else String.Empty
let inline (@<) (x : XElement) n v = x.SetAttributeValue(!> n, v)

type XE = XElement IEnumerator

let inline bbox e    = (e @ "bbox") |> fun s -> s.Split [| ' ' |] |> Seq.map float |> Seq.toList
let inline left bbox = match bbox with l::_ -> l | _ -> nan

let mark n = let id = Guid.NewGuid() in Seq.iter <| fun e -> e @< "class-" + n <| id

let speaker  (n : XE) =
  let  c1 = n.Current
  if   c1 @? "font" <| "Bold"
  then let  l1 = c1 |> bbox |> left
       while n.MoveNext() && ~~(n.Current @ "s") do ()
       let  c2 = n.Current
       if  (c2 @ "font") -?- "Bold" |> not
       then let  l2 = c2 |> bbox |> left
            if   l1 < l2
            then let  s2 = c2 @ "s"
                 if        s2 -?- "("
                 then if   s2 -?- ")"
                      then [c1; c2] |> mark "speaker"
                      while n.MoveNext() && ~~(n.Current @ "s") do ()
                      let  c3 = n.Current
                      if  (c3 @ "font") -?- "Bold" |> not
                      then let  l3 = c3 |> bbox |> left
                           if   l1 < l3
                           then if   (c3 @ "s") -?- ")"
                                then [c1; c2; c3] |> mark "speaker"  


let test (x : XElement) =
  let spans = x.Descendants(!> "span") |> Seq.toArray
  for i = 29 to spans.Length - 1 do
    let n = (spans |> Seq.skip i).GetEnumerator()
    n.MoveNext() |> ignore
    speaker n

输入

<doc>
<block bbox="63.2999 550.846 246.865 561.875">
  <line bbox="63.2999 550.846 246.865 561.875">
    <span bbox="63.2999 550.846 189.001 561.875" font="TimesNewRoman,Bold" size="9.96" s="Dr. Frank-Walter Steinmeier " />
    <span bbox="189 550.846 246.865 561.875" font="TimesNewRoman" size="9.96" s="(SPD)  . . . . . ." />
  </line>
</block>
<block bbox="63.2999 567.766 246.875 578.796">
  <line bbox="63.2999 567.766 246.875 578.796">
    <span bbox="63.2999 567.766 136.004 578.796" font="TimesNewRoman,Bold" size="9.96" s="Rainer Brüderle " />
    <span bbox="136.02 567.766 246.875 578.796" font="TimesNewRoman" size="9.96" s="(FDP) . . . . . . . . . . . . . . . . ." />
  </line>
</block>
<block bbox="63.2999 584.626 250.351 651.456">
  <line bbox="63.2999 584.626 246.826 595.656">
    <span bbox="63.2999 584.626 152.105 595.656" font="TimesNewRoman,Bold" size="9.96" s="Sahra Wagenknecht " />
    <span bbox="152.16 584.626 246.826 595.656" font="TimesNewRoman" size="9.96" s="(DIE LINKE)  . . . . . . ." />
  </line>
  <line bbox="63.2999 600.362 250.351 613.34">
    <span bbox="63.2999 601.546 139.327 612.576" font="TimesNewRoman,Bold" size="9.96" s="Siegfried Kauder " />
    <span bbox="139.38 601.546 247.762 612.576" font="TimesNewRoman" size="9.96" s="(Villingen-Schwenningen) " />
    <span bbox="247.861 600.362 250.351 613.34" font="Symbol" size="9.96" s=" " />
  </line>
  <line bbox="74.6404 612.526 246.911 623.556">
    <span bbox="74.6404 612.526 246.911 623.556" font="TimesNewRoman" size="9.96" s="(CDU/CSU) . . . . . . . . . . . . . . . . . . . . . . . ." />
  </line>
  <line bbox="63.2999 628.202 191.909 641.18">
    <span bbox="63.2999 629.386 126.374 640.416" font="TimesNewRoman,Bold" size="9.96" s="Jürgen Trittin " />
    <span bbox="126.419 629.386 189.433 640.416" font="TimesNewRoman" size="9.96" s="(BÜNDNIS 90/" />
    <span bbox="189.419 628.202 191.909 641.18" font="Symbol" size="9.96" s=" " />
  </line>
  <line bbox="74.6394 640.426 246.813 651.456">
    <span bbox="74.6394 640.426 246.813 651.456" font="TimesNewRoman" size="9.96" s="DIE GRÜNEN)  . . . . . . . . . . . . . . . . . . . . ." />
  </line>
</block>
</doc>

输出

<doc>
<block>
  <line>
    <span font="TimesNewRoman,Bold" size="9.96" s="Dr. Frank-Walter Steinmeier " class-speaker="1f2e4dca-80d5-4c5e-91b6-6bd2e4a8acaf" />
    <span font="TimesNewRoman" size="9.96" s="(SPD)  . . . . . ." class-speaker="1f2e4dca-80d5-4c5e-91b6-6bd2e4a8acaf" />
  </line>
</block>
<block>
  <line>
    <span font="TimesNewRoman,Bold" size="9.96" s="Rainer Brüderle " class-speaker="eaa75d02-0ac6-4480-bcbe-f17bddfe6e81" />
    <span font="TimesNewRoman" size="9.96" s="(FDP) . . . . . . . . . . . . . . . . ." class-speaker="eaa75d02-0ac6-4480-bcbe-f17bddfe6e81" />
  </line>
</block>
<block>
  <line>
    <span font="TimesNewRoman,Bold" size="9.96" s="Sahra Wagenknecht " class-speaker="6b193f23-9b8b-4b37-9118-d8488fba25a2" />
    <span font="TimesNewRoman" size="9.96" s="(DIE LINKE)  . . . . . . ." class-speaker="6b193f23-9b8b-4b37-9118-d8488fba25a2" />
  </line>
  <line>
    <span font="TimesNewRoman,Bold" size="9.96" s="Siegfried Kauder " class-speaker="a0162e4e-1167-412a-ac11-ac13ef1aa46e" />
    <span font="TimesNewRoman" size="9.96" s="(Villingen-Schwenningen) " class-speaker="a0162e4e-1167-412a-ac11-ac13ef1aa46e" />
    <span font="Symbol" size="9.96" s=" " />
  </line>
  <line>
    <span font="TimesNewRoman" size="9.96" s="(CDU/CSU) . . . . . . . . . . . . . . . . . . . . . . . ." class-speaker="a0162e4e-1167-412a-ac11-ac13ef1aa46e" />
  </line>
  <line>
    <span font="TimesNewRoman,Bold" size="9.96" s="Jürgen Trittin " class-speaker="81fd6735-c57f-464b-a08f-7e7cb3bccfa8" />
    <span font="TimesNewRoman" size="9.96" s="(BÜNDNIS 90/" class-speaker="81fd6735-c57f-464b-a08f-7e7cb3bccfa8" />
    <span font="Symbol" size="9.96" s=" " />
  </line>
  <line>
    <span font="TimesNewRoman" size="9.96" s="DIE GRÜNEN)  . . . . . . . . . . . . . . . . . . . . ." class-speaker="81fd6735-c57f-464b-a08f-7e7cb3bccfa8" />
  </line>
</block>
</doc>

问题

为了自动从简洁的模式声明转到运行代码,我正在考虑执行以下操作:

  • 使用 FParsec 将模式声明解析为 AST
  • 评估 AST

但在我做任何事情之前,我想知道:

  1. 任何人都可以编写(应用)EDSL(/其中的一部分)来直接使用 F# 函数和组合来声明代码,而无需求助于 AST?
  2. 是否有能够在 XML 上进行类似模式匹配的库?
  3. 有人对我的方法有任何意见吗?
4

3 回答 3

4

我不完全确定问题是什么。您是否想使用一些现有的模式匹配语言来处理已经存在的 XML(如您的示例中所示),或者您是否通常询问有关 XML 处理的问题?

LINQ to XML正如 Jack P. 所说,在 F# 中处理 XML 的最佳选择可能是仅使用标准Seq函数。这有点长,但它是非常可读的。

XPath另一种选择是使用 XPath,它是在 XML 中选择节点的非常简洁(并且完全标准)的方式。例如,要选择一个具有指定属性值的节点,可以这样写:

#r "System.Xml.Linq"
open System.Xml.Linq
open System.Xml.XPath

let doc = XDocument.Parse("<r><a n=\"f\">foo</a><a n=\"b\">bar</a></r>")
doc.XPathSelectElement("//a[@*='f']").Value

特定领域的语言如果您想编写自己的简洁语言,那么您不一定要使用自定义解析器等。仅以巧妙的方式使用 F# 运算符就可以取得相当大的进展。这个F# 片段就是一个很好的例子。您可以编写如下内容:

printfn "Matches: %A" (!/foo/bar/(baz@=true)/quux/= x)
printfn "Doesn't Match: %A" (!/foo/bar/(baz@=false)/quux/= x)
printfn "Values: %A" (!/foo/bar/quux/(baz@=42)/! x)

请注意,模式由 F# 编译器进行类型检查。

于 2012-09-10T08:57:03.560 回答
2

IMO,你可以实现这样的最好方法是将它放在 Xlinq(LINQ-to-XML)之上。与“手动”实现所有模式匹配逻辑相比,它可能更易于编码、更易于维护并提供等效(如果不是更好)的性能。

请注意,您仍然可以使用 DSL 来定义要匹配的模式。基本上,您将使用 FParsec 将您的 DSL 解析为 AST,然后遍历 AST 并将其转换为等效的 LINQ 查询(请参阅参考资料System.Linq.Expressions namespace)。一旦获得了表示查询的 LINQ 表达式,您就可以将其应用于任意数量的XDocuments 以执行模式匹配。

您可能还对阅读 Erik Meijer 的论文XLinq: XML Programming Refactored (The Return Of The Monoids)感兴趣,该论文从功能设计的角度讨论了 XML 编程。

于 2012-09-09T14:40:56.077 回答
1

对于问题 2:

我不认为我理解 F# 代码,但我用 Pascal编写了一个 xml 模式匹配库(在线示例cli 示例),您可能想查看它。(虽然我称这些模式为“模板”,它只选择 xml 节点,而不是修改它们)。

使用我的模板,这

<doc>
<block>
  <line>
    <span>{.}</span>*
  </line>*
</block>*
</doc>

将匹配输入中的所有跨度。(会<span>{.}</span>*

或者另一个例子:

<doc>
<block>
  <line>
    <span font="TimesNewRoman,Bold">{@s}</span>*
  </line>*
</block>*
</doc>

将匹配包含说话者姓名的属性。

于 2012-09-09T16:04:13.173 回答