如何防止yaml-cpp解析器剥离所有注释? [英] How can you keep yaml-cpp parser from stripping out all comments?
问题描述
我有一个项目,需要读取记录良好的 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 toload_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屋!