如何防止yaml-cpp解析器剥离所有注释? [英] How can you keep yaml-cpp parser from stripping out all comments?

查看:120
本文介绍了如何防止yaml-cpp解析器剥离所有注释?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个项目,需要读取记录良好的 yaml 文件,修改几个值,然后将其写回。问题在于 yaml-cpp 会完全删除所有注释并吃掉它们。有趣的是, YAML :: Emitter 类允许一个向输出添加注释。有没有一种方法可以将注释保留在输入中,然后将其写回到我看不见的库中?因为就目前而言,使用 YAML :: Parser 类(使用 YAML :: Scanner 类,其中的注释实际上是被吃掉的)。

解决方案

根据 YAML规范


注释是表示的详细信息,对序列化树或表示图没有任何影响


因此使解析器不符合要求以保留注释,如果yaml-cpp这样做,则应在文档中明确说明。



我在 ruamel中为Python做到了这一点.yaml 。如果可以接受从C ++程序嵌入和调用Python,则可以执行以下操作(我在Linux Mint下使用Python 3.5):



pythonyaml.cpp

  #include< Python.h> 

int
update_yaml(const char * yif,const char * yof,const char * obj_path,int val)
{
PyObject * pName,* pModule,* pFunc;
PyObject * pArgs,* pValue;
const char * modname = update_yaml;
const char * lus = load_update_save;

Py_Initialize();
//将当前目录添加到搜索路径
PyObject * sys_path = PySys_GetObject( path);
PyList_Append(sys_path,PyUnicode_FromString(。));

pName = PyUnicode_DecodeFSDefault(modname);
/ *遗漏了pName的错误检查* /

pModule = PyImport_Import(pName);
Py_DECREF(pName);

if(pModule!= NULL){
pFunc = PyObject_GetAttrString(pModule,lus);
/ * pFunc是新引用* /

if(pFunc&& PyCallable_Check(pFunc)){
pArgs = PyTuple_New(4);
PyTuple_SetItem(pArgs,0,PyUnicode_FromString(yif));
PyTuple_SetItem(pArgs,1,PyUnicode_FromString(yof));
PyTuple_SetItem(pArgs,2,PyUnicode_FromString(obj_path));
PyTuple_SetItem(pArgs,3,PyLong_FromLong(val));

pValue = PyObject_CallObject(pFunc,pArgs);
Py_DECREF(pArgs);
if(pValue!= NULL){
printf(旧值:%ld\n,PyLong_AsLong(pValue));
Py_DECREF(pValue);
}
else {
Py_DECREF(pFunc);
Py_DECREF(pModule);
PyErr_Print();
fprintf(stderr,通话失败\n);
返回1;
}
}
else {
if(PyErr_Occurred())
PyErr_Print();
fprintf(stderr,找不到函数\%s\ \n,lus);
}
Py_XDECREF(pFunc);
Py_DECREF(pModule);
}
else {
PyErr_Print();
fprintf(stderr,无法加载\%s\ \n,modname);
返回1;
}
Py_Finalize();
返回0;
}


int
main(int argc,char * argv [])
{
const char * yaml_in_file =输入。 yaml;
const char * yaml_out_file = output.yaml;
update_yaml(yaml_in_file,yaml_out_file, abc.1.klm,42);
}

创建 Makefile (适应从源代码编译的Python3.5安装路径,该路径需要安装标头,否则,您需要安装软件包 python3-dev ) :

  echo -e SRC:= pythonyaml.cpp\n\ncompile:\n\tgcc \ $(SRC)$(/ opt / python / 3.5 / bin / python3-config --cflags --ldflags | tr --delete'\n'| sed's / -Wstrict-prototypes //')-o pythonyaml > Makefile 

使用 make 编译程序。 / p>

创建 update_yaml.py ,将由 pythonyaml 加载:

 #编码:utf-8 

导入跟踪
导入ruamel.yaml


def set_value(data,key_list,value):
键列表是访问嵌套字典和列表
字典键的集合键字符串,列表键必须可转换为整数

键= key_list.pop(0)
如果isinstance(data,list):
键= int(键),如果len(key_list)== 0,则
项=数据[键]

数据[键] =值
返回项
返回set_value(item, key_list,值)


def load_update_save(yaml_in,yaml_out,obj_path,值):
尝试:
如果不是isinstance(obj_path,list):
obj_path = obj_path.split('。')
以open(yaml_in)as fp:
数据= ruamel.yaml.round_trip_load(fp)
res = set_value(数据,obj_path.split('。'),值)
,其中open(yaml_out,'w')为fp:
ruamel.yaml.round_trip_dump(data,fp)
return res
例外情况为e:
print('Exception',e)
traceback.print_exc()#获取如果您的python错误

创建 input.yaml

  abc:
-列表
的第零项-klm:- 999#答案?
xyz:最后一个条目#其他评论

如果您有 ruamel .yaml 安装在python3.5中并运行。/python_yaml 它将打印旧值:-999 ,新文件 output.yaml 将包含:

  abc:
-列表
的第零项-klm:42#答案是?
xyz:最后一个条目#另一个注释




  • 尽管 42 只有两个字符,其中 -999 有四个字符,注释仍与下面的字符对齐

  • 无需提供虚线路径 abc.1.klm ,而是可以在C ++中创建Python列表
    并将其交给 load_update_save()作为第三个参数。在这种情况下,您可以使用键,而不是字符串,或者键是包含点的字符串。

  • 根据您的用法,您可能要更改设置的硬编码假设值的整数(第四个参数为 PyLong_FromLong )。 python程序不需要为此进行更新。

  • 您可以使用相同的文件名进行输入和输出,以覆盖输入。

  • 可以使用 ruamel.yaml


从python文件中更改注释

I have a project that needs to read a well documented yaml file, modify a couple of values, and write it back out. The trouble is that yaml-cpp completely strips out all comments and "eats" them. The interesting thing is that the YAML::Emitter class allows one to add comments to the output. Is there a way to preserve the comments in the input and write them back in the library that I'm not seeing? Because as it stands right now, I can't see any way using the YAML::Parser class (which uses the YAML::Scanner class, where the comments themselves are actually "eaten").

解决方案

According to the YAML spec

Comments are a presentation detail and must not have any effect on the serialization tree or representation graph

So you need to make the parser non-compliant to preserve comments, and if yaml-cpp did that, they should clearly state so in the documentation.

I did this for Python in ruamel.yaml. If embedding and calling Python from your C++ program is acceptible you could do something like the following (I used Python 3.5 for this under Linux Mint):

pythonyaml.cpp:

#include <Python.h>

int
update_yaml(const char*yif, const char *yof, const char* obj_path, int val)
{
    PyObject *pName, *pModule, *pFunc;
    PyObject *pArgs, *pValue;
    const char *modname = "update_yaml";
    const char *lus = "load_update_save";

    Py_Initialize();
    // add current directory to search path
    PyObject *sys_path = PySys_GetObject("path");
    PyList_Append(sys_path, PyUnicode_FromString("."));

    pName = PyUnicode_DecodeFSDefault(modname);
    /* Error checking of pName left out */

    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if (pModule != NULL) {
        pFunc = PyObject_GetAttrString(pModule, lus);
        /* pFunc is a new reference */

        if (pFunc && PyCallable_Check(pFunc)) {
            pArgs = PyTuple_New(4);
            PyTuple_SetItem(pArgs, 0, PyUnicode_FromString(yif));
            PyTuple_SetItem(pArgs, 1, PyUnicode_FromString(yof));
            PyTuple_SetItem(pArgs, 2, PyUnicode_FromString(obj_path));
            PyTuple_SetItem(pArgs, 3, PyLong_FromLong(val));

            pValue = PyObject_CallObject(pFunc, pArgs);
            Py_DECREF(pArgs);
            if (pValue != NULL) {
                printf("Old value: %ld\n", PyLong_AsLong(pValue));
                Py_DECREF(pValue);
            }
            else {
                Py_DECREF(pFunc);
                Py_DECREF(pModule);
                PyErr_Print();
                fprintf(stderr,"Call failed\n");
                return 1;
            }
        }
        else {
            if (PyErr_Occurred())
                PyErr_Print();
            fprintf(stderr, "Cannot find function \"%s\"\n", lus);
        }
        Py_XDECREF(pFunc);
        Py_DECREF(pModule);
    }
    else {
        PyErr_Print();
        fprintf(stderr, "Failed to load \"%s\"\n", modname);
        return 1;
    }
    Py_Finalize();
    return 0;
}


int
main(int argc, char *argv[])
{
    const char *yaml_in_file = "input.yaml";
    const char *yaml_out_file = "output.yaml";
    update_yaml(yaml_in_file, yaml_out_file, "abc.1.klm", 42);
}

Create a Makefile (adapt the path to your Python3.5 installation, which needs to have the headers installed, as is normal if compiled from source, otherwise you need the package python3-dev installed):

echo -e "SRC:=pythonyaml.cpp\n\ncompile:\n\tgcc \$(SRC) $(/opt/python/3.5/bin/python3-config --cflags --ldflags | tr --delete '\n' | sed 's/-Wstrict-prototypes//') -o pythonyaml"  > Makefile

compile the program with make.

Create update_yaml.py which will be loaded by pythonyaml:

# coding: utf-8

import traceback
import ruamel.yaml


def set_value(data, key_list, value):
    """key list is a set keys to access nested dict and list
    dict keys are assumed to be strings, keys for a list must be convertable to integer
    """
    key = key_list.pop(0)
    if isinstance(data, list):
        key = int(key)
    item = data[key]
    if len(key_list) == 0:
        data[key] = value
        return item
    return set_value(item, key_list, value)


def load_update_save(yaml_in, yaml_out, obj_path, value):
    try:
        if not isinstance(obj_path, list):
            obj_path = obj_path.split('.')
        with open(yaml_in) as fp:
            data = ruamel.yaml.round_trip_load(fp)
        res = set_value(data, obj_path.split('.'), value)
        with open(yaml_out, 'w') as fp:
            ruamel.yaml.round_trip_dump(data, fp)
        return res
    except Exception as e:
        print('Exception', e)
        traceback.print_exc()  # to get some useful feedback if your python has errors

Create input.yaml:

abc:
  - zero-th item of list
  - klm: -999        # the answer?
    xyz: last entry  # another comment

If you have ruamel.yaml installed in your python3.5 and run ./python_yaml it will print Old value: -999, and the new file output.yaml will contain:

abc:
- zero-th item of list
- klm: 42            # the answer?
  xyz: last entry    # another comment

  • although 42 has only two characters where -999 has four, the comment still aligns with the one below it
  • instead of providing a dotted path abc.1.klm you can create a Python list in C++, and hand that to load_update_save() as third parameter. In that case you can have keys that are other items than strings, or keys that are a string that contains a dot
  • depending on your usage you might want to change the hard coded assumption of setting an integer (PyLong_FromLong for the fourth parameter) for the value. The python program doesn't need updating for that.
  • you can use the same file_name for input and output, to overwrite the input.
  • it is possible to change the comment from the python file using ruamel.yaml

这篇关于如何防止yaml-cpp解析器剥离所有注释?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆