2016-09-24 61 views
5

Mam projekt, który musi przeczytać dobrze udokumentowany plik yaml, zmodyfikować kilka wartości i zapisać go z powrotem. Problem polega na tym, że yaml-cpp całkowicie usuwa wszystkie komentarze i "je" je. Ciekawe jest to, że klasa YAML::Emitter pozwala dodawać komentarze do wyników. Czy istnieje sposób na zachowanie komentarzy na wejściu i zapisanie ich w bibliotece, której nie widzę? Ponieważ w obecnej formie nie widzę żadnej możliwości korzystania z klasy YAML::Parser (która używa klasy YAML::Scanner, gdzie same komentarze są "zjedzone").Jak można zachować parser yaml-cpp z usuwania wszystkich komentarzy?

Odpowiedz

1

Według YAML spec

Komentarze są szczegółowo prezentacji i nie może mieć żadnego wpływu na drzewie serializacji lub wykresu reprezentacji

więc trzeba uczynić parser niezgodność zachowania komentarze, a jeśli yaml-cpp to zrobił, powinny to jasno określić w dokumentacji.

Zrobiłem to dla Pythona w ruamel.yaml. Jeśli osadzanie i wywołanie Pythona z C++ Program jest acceptible mógłby zrobić coś jak poniżej (użyłem Python 3.5 dla tego pod 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); 
} 

Tworzenie Makefile (dostosować ścieżkę do instalacji Python3.5, który musi mieć zainstalowane nagłówki, ponieważ jest to normalne jeśli kompilowany ze źródeł, w przeciwnym razie trzeba pakiet python3-dev zainstalowany):

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 

skompilować program z make.

Tworzenie update_yaml.py który zostanie załadowany przez 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 

Tworzenie input.yaml:

Jeśli masz ruamel.yaml zainstalowany w python3.5 i uruchomić ./python_yaml zostanie wydrukowana Old value: -999 i nowy plik output.yaml będzie zawierać:

abc: 
- zero-th item of list 
- klm: 42   # the answer? 
    xyz: last entry # another comment 
  • chociaż 42 ma tylko dwa znaki gdzie -999 ma cztery, komentarz nadal wyrównany jeden poniżej niego
  • zamiast zapewniać kropkowaną ścieżkę abc.1.klm można utworzyć listę Pythona w C++, a strony, które do load_update_save() jako trzeci parametr. W takim przypadku możesz mieć klucze, które są innymi elementami niż łańcuchy lub klucze, które są ciągiem zawierającym kropkę, w zależności od twojego użycia, możesz chcieć zmienić sztywno zakodowane założenie ustawienia liczby całkowitej (PyLong_FromLong dla czwartego parametru) dla wartości. Program Pythona nie wymaga aktualizacji.
  • możesz użyć tej samej nazwy pliku dla wejścia i wyjścia, aby nadpisać dane wejściowe.
  • to jest można zmienić komentarz z pliku Pythona przy użyciu ruamel.yaml
+0

Zdecydowaliśmy się wodnika próbuje zachować komentarze w pliku YAML jesteśmy analizowania. –