【问题标题】:XPath-3 CSV generationXPath-3 CSV 生成
【发布时间】:2022-12-18 20:35:49
【问题描述】:

我正在尝试使用 XPath 3.0 (xidel --xpath) 将以下 XML 转换为 CSV:

<?xml version="1.0" encoding="utf-8" ?>
<csv>
    <record>
        <field1>A</field1>
        <field2>B</field2>
        <field3>C</field3>
    </record>
    <record>
        <field2> </field2>
        <field3></field3>
    </record>
    <record>
        <field1>,,</field1>
        <field2>""</field2>
        <field3>..</field3>
        <field3>.
                 .</field3>
    </record>
</csv>

我的预期输出是:

field1,field2,field3
A,B,C
, ,""
",,","""""",".
                 ."

我遇到了一些问题(第一个不是xidel 特有的):

  1. 我用 distinct-values(/csv/record/*/name()) 得到字段名称;如何使用该序列提取记录中的数据?

  2. 我想区分一个丢失的空的字段,但是 xideltext() 选择器似乎并不关心这个;它是 XPath 功能还是 xidel 错误?

  3. 我不能让return工作; xidel 使用不同的语法吗?

    更新

    我自己解决了#1,@ConalTuohy 在他的评论中解决了#2。

    这是我现在得到的:

    #!/bin/bash
    IFS='' read -r -d '' xpath <<'EOF'
    
    let $csv-escape-string := function($str as xs:string) as xs:string {
        if ( matches( $str, ',|"|\n' ) )
        then
            concat('"',replace($str,'"','""'),'"')
        else
            $str
    },
    $fields-names := distinct-values(/csv/record/*/name()),
    $csv := (
        string-join( $fields-names, ',' ),
        /csv/record/string-join(
            (
                for $fn in $fields-names
                return $csv-escape-string(string( *[name()=$fn][last()]/text() ))
            ), ','
        )
    )
    return $csv
    
    EOF
    xidel --xpath "$xpath" file.xml
    

    但是输出不是我想要的:

    field1,field2,field3
    A,B,C
    ,,
    ",,","""""",".
                     ."
    

    有人可以尝试使用其他 XPath-3 处理器来确保它是 xidel 正在规范化 text() 吗?

【问题讨论】:

  • 我不知道 xidel,但您的 XPath 表达式应该以 let 关键字开头以进行这些变量赋值,并以 return 语句结尾。
  • @ConalTuohy 我更新了我的代码;您手边有用于试用的 XPath-3 处理器吗?

标签: xml csv xpath xidel


【解决方案1】:

但是输出不是我想要的

您的 xidel 二进制文件很可能太旧了。

xidel --xpath "$xpath" file.xml

您将查询放在 $xpath 中的原因是因为单引号和双引号,我猜?这确实是规避此问题的好方法,但如果您对字符串使用双引号则不需要。 --xpath "function('string')" 用于,而--xpath 'function("string")' 用于。要转义字符串中的双引号,只需输入第二个:

$ xidel -s input.xml -e '
  let $field-names:=distinct-values(csv/record/*/name()) return (
    join($field-names,","),
    csv/record/join(
      for $fn in $field-names return
      string(*[name()=$fn][last()]) ! (
        if (matches(.,",|""|
"))
        then concat("""",replace(.,"""",""""""),"""")
        else .
      ),","
    )
  )
'

或者,您可以使用 XQuery 表示法:

if (matches(.,",|&quot;|
"))
then concat("&quot;",replace(.,"&quot;","&quot;&quot;"),"&quot;")
else .

您还可以使用 xidel 自己的“扩展字符串”语法(并使用另一个 let 变量代替 !):

$ xidel -s input.xml -e '
  let $field-names:=distinct-values(csv/record/*/name()) return (
    join($field-names,","),
    csv/record/join(
      for $fn in $field-names
      let $val:=string(*[name()=$fn][last()])
      return
      if (matches($val,",|""|
"))
      then x"""{replace($val,"""","""""")}"""
      else $val,
      ","
    )
  )
'

【讨论】:

  • 我在 Linux 上测试了Xidel 0.9.8,它仍然去除了前导和尾随空格:-(
  • @Fravadona v0.9.8 不是最新版本。 videlibri.sourceforge.net/xidel.html#downloads:《推荐使用0.9.9版本》.
  • 用于 macOS 的 v0.9.9 预构建二进制文件在此示例中崩溃。使用fpc 3.2.X 编译xidel 后出现同样的问题;我必须将 fpc 降级到版本 3.0.4a 才能让 xidel 在 macOS 上工作。
  • @Fravadona 在那种情况下,我认为你最好正式report 这个,因为我自己不是 macOS 用户(Benito 也不是,xidel 的作者)。
【解决方案2】:

当我在 XML Workbench 上运行你的代码时,我得到了以下结果:

A,B,C
, ,
",,","""""",".
                 ."

注意,我将查询的最后一行更改为 return string-join($csv, codepoints-to-string(10)) 以将整个 CSV 作为单个字符串返回,因此 XML Workbench 不会单独引用结果序列中的每个字符串,否则会这样。

【讨论】:

  • 坦克你指出这一点。输出是有效的(第二行的空格没有去掉)所以确实有问题xidel。我让它按预期与 XML_Workbench 一起工作,所以现在我要寻找其他 XPath-3 处理器(并将错误报告给xidel
  • 祝你好运! XML Workbench 基于来自 Saxonica 的 SaxonJS,后者也为其他平台提供 XSLT 3 处理器。 saxonica.com/products/products.xml
【解决方案3】:

后来我做了更好的选择


  1. 仅输出带引号的字段:
    let $records := //record,
    $fields := distinct-values( $records/*/local-name() )
    return (
        string-join( $fields, "," ),
        for $r in $records return
        string-join(
            for $f in $fields return
            """" || replace($r/*[local-name()=$f][last()],"""","""""") || """",
            ","
        )
    )
    
    field1,field2,field3
    "A","B","C"
    ""," ",""
    ",,","""""",".
                     ."
    

    1. 在需要时引用字段并区分 nullempty 值:
    let $csv-escape-string := function($str as xs:string?) as xs:string {
        if ( not(exists($str)) ) then
            ""
        else if ( $str = "" ) then
            """"""
        else if ( matches( $str, ",|""""|
    " ) ) then
            """" || replace($str,"""","""""") || """"
        else
            $str
    },
    $records := //record,
    $fields := distinct-values( $records/*/local-name() )
    return (
        string-join( $fields, "," ),
        for $r in $records return
        string-join(
            for $f in $fields return
            $csv-escape-string($r/*[local-name()=$f][last()]),
            ","
        )
    )
    
    field1,field2,field3
    A,B,C
    , ,""
    ",,","""""",".
                     ."
    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-23
    • 1970-01-01
    • 2021-04-08
    相关资源
    最近更新 更多