#json-toml #json #toml #xml #convert-json #xml-format #ivoa

votable

Rust 实现的 VOTable 序列化/反序列化库,支持 XML 以外的格式,如 JSON、TOML 或 YAML

10 个版本 (5 个重大更改)

0.6.0 2024年4月5日
0.5.0 2024年3月11日
0.4.0 2024年2月7日
0.3.0 2024年1月12日
0.1.1-alpha2022年10月10日

#249编码

Apache-2.0 OR MIT

720KB
19K SLoC

votableVOTLibRust

一个库,用于在 Rust 中构建、读取和写入 VOTables,同时高效地在 JSON、YAML、TOML、XML-TABLEDATA、XML-BINARY 和 XML-BINARY2 之间进行转换,同时保留所有元素(除注释外)及其顺序。

API Documentation on docs.rs BenchLib

VOT Lib Rust 用于

状态

代码包含所有主要功能

  • 支持 BINARY、BINARY2 格式
  • 支持行中的 CDATA,支持 StAX 模式进行流式处理
  • 支持 MIVOT 块解析

但它离我想要的干净和详细的文档还有很长的路要走。如果您想在 Rust 项目中使用此包,您可能需要查看 VOTCli 代码。

我们仍然(合理地)开放于更改,例如

  • 我们可以使用 '@' 前缀标记属性
  • 我们可以使用大写元素标签名
  • 我们可以从元素数组中移除 's' 后缀
  • 我们可以将 post_infos 的名称更改为其他名称
  • ...

需要更多的测试,特别是位类型和数组。请提供您的 VOTable 示例!

为什么除了 XML 之外还要使用 JSON、TOML、YAML

VOTable 是一个基于 XML 的格式。为什么还有其他格式?

  • JSON:为了在 Web 浏览器中轻松操作 VOTable 数据,因为 JSON 表示 JavaScript 对象(所有浏览器都原生地将 JSON 解析为 JavaScript 对象)。
  • TOML:为了轻松手动更新 VOTables(尤其是 VOTables 的元数据部分)。此外,它相当紧凑。
  • YAML:因为有些人喜欢它,而且实现起来几乎免费(多亏了serde)。

动机

  • 在新的CDS内部工具 qat2s 中原生支持VOTable格式(具有多线程能力的查询和操作可能大型目录的工具)。
  • 以用户友好的格式(TOML)存储VizieR(大型)目录丰富的元数据,同时能够返回与VizieR相同的VOTable头部(不使用数据库连接)。
    • 针对 qat2sExXmatchprogressive catalogue
  • Aladin Lite V3添加Rust VOTable解析和写入库
  • 确保静态地在内存中构建的结构符合VOTable规范
  • ...

设计选择和问题

尽管默认提供的从JSON/YAML/TOML转换的实现并不关注性能(因为我们不使用VOTable FIELDS信息,而是在第一个随后的VOTableValue中反序列化每个表字段(见votable::impls::Schema.serialize_seed)),但性能似乎仍然很好。

VOT Lib大量依赖serde

这个库被设计用来在将XML、JSON、...等来回转换时保留VOTable TAGs的顺序。
但是,到目前为止,XML注释被忽略并丢失。

在JSON/TOML/YAML中,对于VOTABLE和RESOURCE元素,我们在RESOURCE元素(s)前后将INFO块分开。我们使用infos(仅针对RESOURCE)和post_infos数组。引用IVO文档

INFO元素可以出现在/TABLE、/RESOURCE和/VOTABLE关闭标签之前(启用后操作诊断)

(我们想知道在VOTable中后操作诊断是否不应该有一个不同于INFO的名字)。

在JSON/TOML/YAML中,对于VOTABLE、RESOURCE、TABLE和GROUP元素,我们将"open bullet"(见VOTable标准 7.1)元素分组在一个包含对象的elements数组中,这些对象的"elem_type"属性设置为以下之一:InfoFieldCoosysTimesysGroupParam、...

在内部,我们将VOTABLE和RESOURCE中的GROUP与TABLE中的GROUP区分开来,因为在后者的情况下,GROUP可能包含FIELDRef。

RESOURCE中,我们将LINK、子RESOURCETABLEINFO一起打包到ResourceSubElem结构中。

在JSON/TOML/YAML中,属性和子元素名称之间没有区别(全部采用驼峰式)。

警告

  • TOML不支持null(我们至今将null值转换为空字符串)。
  • 从TOML/JSON/YAML转换为需要将所有数据加载到内存中,不适用于大文件。

从VOTable到JSON的其他转换方法

Laurent Michel在处理VOTables中模型注释的上下文中(MIVOT)练习了XML2JSON转换。用例是将序列化为XML的模型实例转换为JSON消息。
转换使用了标准的Python工具(xmltodic模块)。下面的代码是从客户端代码项目提取的。需要注意的是,转换规则并非PYTHON(或VOTable)特定,它们也被实现在例如XSLT中。

import os
import xmltodict
import json
import numpy
from lxml import etree

class MyEncoder(json.JSONEncoder):

  def default(self, obj):
    if isinstance(obj, numpy.integer):
      return int(obj)
    elif isinstance(obj, numpy.floating):
      return float(obj)
    elif isinstance(obj, numpy.ndarray):
      return obj.tolist()
    else:
      return super(MyEncoder, self).default(obj)

data_path = os.path.dirname(os.path.realpath(__file__))

xml_block = etree.parse(os.path.join(data_path, "votable_to_json.xml"))
raw_json = xmltodict.parse(etree.tostring(xml_block))
pretty_json = json.dumps(raw_json, indent=2, cls=MyEncoder)
print(pretty_json)

with open(os.path.join(data_path, "votable_to_json.json"), 'w') as file:
  file.write(json.dumps(raw_json, indent=2))

优点

  • 标准
  • 只有几行Python代码

不便之处

  • 元素的顺序丢失(特别是INFOs和后处理INFOs)
  • 这是一个单向转换(无法从JSON转换为VOTable)

示例

同一API生成的几个输出都转换成了VOTable。

rust代码(创建VOTable的API)

let rows = vec![
    vec![VOTableValue::Double(f64::NAN), VOTableValue::CharASCII('*'), VOTableValue::Float(14.52)],
    vec![VOTableValue::Double(1.25), VOTableValue::Null, VOTableValue::Float(-1.2)],
];

let data_content = InMemTableDataRows::new(rows);

let table = Table::new()
  .set_id("V_147_sdss12")
  .set_name("V/147/sdss12")
  .set_description("SDSS photometric catalog".into())
  .push_field(
    Field::new("RA_ICRS", Datatype::Double)
      .set_unit("deg")
      .set_ucd("pos.eq.ra;meta.main")
      .set_width(10)
      .set_precision(Precision::new_dec(6))
      .set_description("Right Ascension of the object (ICRS) (ra)".into())
  ).push_field(
    Field::new("m_SDSS12", Datatype::CharASCII)
      .set_ucd("meta.code.multip")
      .set_arraysize(ArraySize::new_fixed_1d(1))
      .set_width(10)
      .set_precision(Precision::new_dec(6))
      .set_description("[*] Multiple SDSS12 name".into())
      .push_link(Link::new().set_href("http://vizier.u-strasbg.fr/viz-bin/VizieR-4?-info=XML&-out.add=.&-source=V/147&SDSS12=${SDSS12}"))
  ).push_field(
    Field::new("umag", Datatype::Float)
      .set_unit("mag")
      .set_ucd("phot.mag;em.opt.U")
      .set_width(6)
      .set_precision(Precision::new_dec(3))
      .set_description("[4/38]? Model magnitude in u filter, AB scale (u) (5)".into())
      .set_values(Values::new().set_null("NaN"))
  ).set_data(Data::new_empty().set_tabledata(data_content));

let resource = Resource::default()
  .set_id("yCat_17011219")
  .set_name("J/ApJ/701/1219")
  .set_description(r#"Photometric and spectroscopic catalog of objects in the field around HE0226-4110"#.into())
  .push_coosys(CooSys::new("J2000", System::new_default_eq_fk5()))
  .push_coosys(CooSys::new("J2015.5", System::new_icrs().set_epoch(2015.5)))
  .push_sub_elem(
    ResourceSubElem::from_table(table)
      .push_info(Info::new("QUERY_STATUS", "OVERFLOW").set_content("truncated result (maxtup=2)"))
  )  
);

let mut votable = VOTable::new(resource)
  .set_id("my_votable")
  .set_version(Version::V1_4)
  .set_description(r#"VizieR Astronomical Server vizier.u-strasbg.fr"#.into())
  .push_info(Info::new("votable-version", "1.99+ (14-Oct-2013)").set_id("VERSION"));

备注:当以BINARYBINARY2格式序列化时,才会检查用户输入的VOTableValue与声明的Fields之间的关联。

VOTable

<?xml version="1.0" encoding="UTF-8"?>
<VOTABLE ID="my_votable" version="1.4">
  <DESCRIPTION>VizieR Astronomical Server vizier.u-strasbg.fr</DESCRIPTION>
  <INFO ID="VERSION" name="votable-version" value="1.99+ (14-Oct-2013)"/>
  <RESOURCE ID="yCat_17011219" name="J/ApJ/701/1219">
    <DESCRIPTION>Photometric and spectroscopic catalog of objects in the field around HE0226-4110</DESCRIPTION>
    <COOSYS ID="J2000" system="eq_FK4" equinox="B2000"/>
    <COOSYS ID="J2015.5" system="ICRS" epoch="J2015.5"/>
    <TABLE ID="V_147_sdss12" name="V/147/sdss12">
      <DESCRIPTION>SDSS photometric catalog</DESCRIPTION>
      <FIELD name="RA_ICRS" datatype="double" unit="deg" precision="6" width="10" ucd="pos.eq.ra;meta.main">
        <DESCRIPTION>Right Ascension of the object (ICRS) (ra)</DESCRIPTION>
      </FIELD>
      <FIELD name="m_SDSS12" datatype="char" precision="6" width="10" ucd="meta.code.multip" arraysize="1">
        <DESCRIPTION>[*] Multiple SDSS12 name</DESCRIPTION>
        <LINK href="http://vizier.u-strasbg.fr/viz-bin/VizieR-4?-info=XML&amp;-out.add=.&amp;-source=V/147&amp;SDSS12=${SDSS12}"/>
      </FIELD>
      <FIELD name="umag" datatype="float" unit="mag" precision="3" width="2" ucd="phot.mag;em.opt.U">
        <DESCRIPTION>[4/38]? Model magnitude in u filter, AB scale (u) (5)</DESCRIPTION>
        <VALUES null="NaN"/>
      </FIELD>
      <DATA>
        <TABLEDATA>
          <TR>
            <TD>NaN</TD>
            <TD>*</TD>
            <TD>14.52</TD>
          </TR>
          <TR>
            <TD>1.25</TD>
            <TD></TD>
            <TD>-1.2</TD>
          </TR>
        </TABLEDATA>
      </DATA>
    </TABLE>
    <INFO name="QUERY_STATUS" value="OVERFLOW">truncated result (maxtup=2)</INFO>
  </RESOURCE>
</VOTABLE>

JSON

{
  "votable": {
    "ID": "my_votable",
    "version": "1.4",
    "description": "VizieR Astronomical Server vizier.u-strasbg.fr",
    "elems": [
      {
        "elem_type": "Info",
        "ID": "VERSION",
        "name": "votable-version",
        "value": "1.99+ (14-Oct-2013)"
      }
    ],
    "resources": [
      {
        "ID": "yCat_17011219",
        "name": "J/ApJ/701/1219",
        "description": "Photometric and spectroscopic catalog of objects in the field around HE0226-4110",
        "elems": [
          {
            "elem_type": "CooSys",
            "ID": "J2000",
            "system": "eq_FK4",
            "equinox": 2000.0
          },
          {
            "elem_type": "CooSys",
            "ID": "J2015.5",
            "system": "ICRS",
            "epoch": 2015.5
          }
        ],
        "sub_elems": [
          {
            "resource_or_table": {
              "elem_type": "Table",
              "id": "V_147_sdss12",
              "name": "V/147/sdss12",
              "description": "SDSS photometric catalog",
              "elems": [
                {
                  "elem_type": "Field",
                  "name": "RA_ICRS",
                  "datatype": "double",
                  "unit": "deg",
                  "precision": "6",
                  "width": 10,
                  "ucd": "pos.eq.ra;meta.main",
                  "description": "Right Ascension of the object (ICRS) (ra)"
                },
                {
                  "elem_type": "Field",
                  "name": "m_SDSS12",
                  "datatype": "char",
                  "precision": "6",
                  "width": 10,
                  "ucd": "meta.code.multip",
                  "arraysize": "1",
                  "description": "[*] Multiple SDSS12 name",
                  "links": [
                    {
                      "href": "http://vizier.u-strasbg.fr/viz-bin/VizieR-4?-info=XML&-out.add=.&-source=V/147&SDSS12=${SDSS12}"
                    }
                  ]
                },
                {
                  "elem_type": "Field",
                  "name": "umag",
                  "datatype": "float",
                  "unit": "mag",
                  "precision": "3",
                  "width": 2,
                  "ucd": "phot.mag;em.opt.U",
                  "description": "[4/38]? Model magnitude in u filter, AB scale (u) (5)",
                  "values": {
                    "null": "NaN"
                  }
                }
              ],
              "data": {
                "data_type": "TableData",
                "rows": [
                  [
                    null,
                    "*",
                    14.52
                  ],
                  [
                    1.25,
                    null,
                    -1.2
                  ]
                ]
              }
            },
            "infos": [
              {
                "name": "QUERY_STATUS",
                "value": "OVERFLOW",
                "content": "truncated result (maxtup=2)"
              }
            ]
          }
        ]
      }
    ]
  }
}

TOML

[votable]
ID = 'my_votable'
version = '1.4'
description = 'VizieR Astronomical Server vizier.u-strasbg.fr'

[[votable.elems]]
elem_type = 'Info'
ID = 'VERSION'
name = 'votable-version'
value = '1.99+ (14-Oct-2013)'

[[votable.resources]]
ID = 'yCat_17011219'
name = 'J/ApJ/701/1219'
description = 'Photometric and spectroscopic catalog of objects in the field around HE0226-4110'

[[votable.resources.elems]]
elem_type = 'CooSys'
ID = 'J2000'
system = 'eq_FK4'
equinox = 2000.0

[[votable.resources.elems]]
elem_type = 'CooSys'
ID = 'J2015.5'
system = 'ICRS'
epoch = 2015.5

[[votable.resources.sub_elems]]
[votable.resources.sub_elems.resource_or_table]
elem_type = 'Table'
id = 'V_147_sdss12'
name = 'V/147/sdss12'
description = 'SDSS photometric catalog'

[[votable.resources.sub_elems.resource_or_table.elems]]
elem_type = 'Field'
name = 'RA_ICRS'
datatype = 'double'
unit = 'deg'
precision = '6'
width = 10
ucd = 'pos.eq.ra;meta.main'
description = 'Right Ascension of the object (ICRS) (ra)'

[[votable.resources.sub_elems.resource_or_table.elems]]
elem_type = 'Field'
name = 'm_SDSS12'
datatype = 'char'
precision = '6'
width = 10
ucd = 'meta.code.multip'
arraysize = '1'
description = '[*] Multiple SDSS12 name'

[[votable.resources.sub_elems.resource_or_table.elems.links]]
href = 'http://vizier.u-strasbg.fr/viz-bin/VizieR-4?-info=XML&-out.add=.&-source=V/147&SDSS12=${SDSS12}'

[[votable.resources.sub_elems.resource_or_table.elems]]
elem_type = 'Field'
name = 'umag'
datatype = 'float'
unit = 'mag'
precision = '3'
width = 2
ucd = 'phot.mag;em.opt.U'
description = '[4/38]? Model magnitude in u filter, AB scale (u) (5)'

[votable.resources.sub_elems.resource_or_table.elems.values]
null = 'NaN'

[votable.resources.sub_elems.resource_or_table.data]
data_type = 'TableData'
rows = [
    [
    nan,
    '*',
    14.52,
],
    [
    1.25,
    '',
    -1.2,
],
]

[[votable.resources.sub_elems.infos]]
name = 'QUERY_STATUS'
value = 'OVERFLOW'
content = 'truncated result (maxtup=2)'

YAML

votable:
  ID: my_votable
  version: '1.4'
  description: VizieR Astronomical Server vizier.u-strasbg.fr
  elems:
    - elem_type: Info
      ID: VERSION
      name: votable-version
      value: 1.99+ (14-Oct-2013)
  resources:
    - ID: yCat_17011219
      name: J/ApJ/701/1219
      description: Photometric and spectroscopic catalog of objects in the field around HE0226-4110
      elems:
        - elem_type: CooSys
          ID: J2000
          system: eq_FK4
          equinox: 2000.0
        - elem_type: CooSys
          ID: J2015.5
          system: ICRS
          epoch: 2015.5
      sub_elems:
        - resource_or_table:
            elem_type: Table
            id: V_147_sdss12
            name: V/147/sdss12
            description: SDSS photometric catalog
            elems:
              - elem_type: Field
                name: RA_ICRS
                datatype: double
                unit: deg
                precision: '6'
                width: 10
                ucd: pos.eq.ra;meta.main
                description: Right Ascension of the object (ICRS) (ra)
              - elem_type: Field
                name: m_SDSS12
                datatype: char
                precision: '6'
                width: 10
                ucd: meta.code.multip
                arraysize: '1'
                description: '[*] Multiple SDSS12 name'
                links:
                  - href: http://vizier.u-strasbg.fr/viz-bin/VizieR-4?-info=XML&-out.add=.&-source=V/147&SDSS12=${SDSS12}
              - elem_type: Field
                name: umag
                datatype: float
                unit: mag
                precision: '3'
                width: 2
                ucd: phot.mag;em.opt.U
                description: '[4/38]? Model magnitude in u filter, AB scale (u) (5)'
                values:
                  'null': NaN
            data:
              data_type: TableData
              rows:
                - - .nan
                  - '*'
                  - 14.52
                - - 1.25
                  - null
                  - -1.2
          infos:
            - name: QUERY_STATUS
              value: OVERFLOW
              content: truncated result (maxtup=2)

示例:遍历VOTable的表格和行

    let mut votable_it = VOTableIterator::from_file("resources/sdss12.vot")?;
    while let Some(mut row_it) = votable_it.next_table_row_value_iter()? {
      let table_ref_mut = row_it.table();
      println!("Fields: {:?}", table_ref_mut.elems);
      for (i, row) in row_it.enumerate() {
        println!("Row {}: {:?}", i, row);
      }
    }
    let votable = votable_it.end_of_it();
    println!("VOTable: {:?}", votable);

待办事项清单

  • 支持CDATA
    • InfoDescriptionLinkPARAMRefFIELDRef中支持
    • <TD></TD>中支持CDATA
  • 编写Rust库的文档(但截至目前,由于Rust在天文学社区中尚未广泛应用,所以...)
  • 添加一个检查方法,确保用户输入的VOTableValue(使用API构建VOTable)与表模式匹配(或自动转换为正确的VOTableValue)
  • 添加更多的测试!
  • 添加将转换为/from TABLEDATABINARYBINARY2的功能
    • 但仍然需要在CLI中实现流模式
  • 在CLI中实现toCSV(但不是fromCSV
  • quick_error替换为anyhowthiserror
  • 提升quick_xml版本,并
    • 实现从&[u8](例如在wasm或memmapped文件中包含全部数据时)的解析,不进行复制
    • 实现异步
  • ...

许可协议

与大多数Rust项目一样,本项目许可协议为以下之一

由您选择。

贡献

除非您明确声明,否则根据Apache-2.0许可协议,您提交给本项目的任何有意包含的贡献都将双许可如上所述,不附加任何其他条款或条件。

依赖项

~6MB
~122K SLoC