[libcamera-devel] [PATCH 3/9] libcamera: Introduce YamlParser as a helper to parse yaml files

Han-Lin Chen hanlinchen at chromium.org
Wed Feb 9 08:19:11 CET 2022


Introduce YamlParser as a helper to convert contents of a yaml file to
a tree based structure for easier reading, and to avoid writing parser
with raw yaml tokens. The class is based on libyaml, and only support
reading but writing a yaml file.

The interface is inspired by Json::Value class from jsoncpp:
http://jsoncpp.sourceforge.net/class_json_1_1_value.html

Signed-off-by: Han-Lin Chen <hanlinchen at chromium.org>
---
 README.rst                               |   2 +-
 include/libcamera/internal/meson.build   |   1 +
 include/libcamera/internal/yaml_parser.h |  82 +++
 src/libcamera/meson.build                |   3 +
 src/libcamera/yaml_parser.cpp            | 796 +++++++++++++++++++++++
 5 files changed, 883 insertions(+), 1 deletion(-)
 create mode 100644 include/libcamera/internal/yaml_parser.h
 create mode 100644 src/libcamera/yaml_parser.cpp

diff --git a/README.rst b/README.rst
index ca8a97cb..4a2a451a 100644
--- a/README.rst
+++ b/README.rst
@@ -60,7 +60,7 @@ Meson Build system: [required]
             pip3 install --user --upgrade meson
 
 for the libcamera core: [required]
-        python3-yaml python3-ply python3-jinja2
+        libyaml-dev python3-yaml python3-ply python3-jinja2
 
 for IPA module signing: [required]
         libgnutls28-dev openssl
diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
index c9e055d4..7a780d48 100644
--- a/include/libcamera/internal/meson.build
+++ b/include/libcamera/internal/meson.build
@@ -42,4 +42,5 @@ libcamera_internal_headers = files([
     'v4l2_pixelformat.h',
     'v4l2_subdevice.h',
     'v4l2_videodevice.h',
+    'yaml_parser.h',
 ])
diff --git a/include/libcamera/internal/yaml_parser.h b/include/libcamera/internal/yaml_parser.h
new file mode 100644
index 00000000..7ba7c52c
--- /dev/null
+++ b/include/libcamera/internal/yaml_parser.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Google Inc.
+ *
+ * yaml_parser.h - libcamera yaml parsing helper
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <libcamera/base/class.h>
+
+#include <libcamera/geometry.h>
+
+namespace libcamera {
+
+class YamlObject
+{
+public:
+	YamlObject() = default;
+	~YamlObject() = default;
+
+	YamlObject(const YamlObject &) = delete;
+	YamlObject &operator=(const YamlObject &) = delete;
+
+	bool isValue() const
+	{
+		return type_ == VALUE;
+	}
+	bool isList() const
+	{
+		return type_ == LIST;
+	}
+	bool isDictionary() const
+	{
+		return type_ == DICTIONARY;
+	}
+
+	bool asBool(bool defaultValue = false, bool *ok = nullptr) const;
+	double asDouble(double defaultValue = 0.0, bool *ok = nullptr) const;
+	int32_t asInt32(int32_t defaultValue = 0, bool *ok = nullptr) const;
+	uint32_t asUint32(uint32_t defaultValue = 0, bool *ok = nullptr) const;
+	std::string asString(std::string defaultValue = "", bool *ok = nullptr) const;
+	Size asSize(Size defaultValue = Size(), bool *ok = nullptr) const;
+
+	int length() const;
+	const YamlObject &operator[](int index) const;
+
+	bool isMember(const std::string &key) const;
+	const YamlObject &get(const std::string &key) const;
+	std::vector<std::string> getMemberNames() const;
+
+private:
+	friend class YamlParser;
+
+	enum PropertyType {
+		DICTIONARY,
+		LIST,
+		VALUE,
+	} type_;
+
+	std::string value_;
+	std::vector<std::unique_ptr<YamlObject>> list_;
+	std::map<const std::string, std::unique_ptr<YamlObject>> dictionary_;
+};
+
+class YamlParser final : public Extensible
+{
+	LIBCAMERA_DECLARE_PRIVATE()
+
+public:
+	YamlParser();
+	int ParseAsYamlObject(FILE *fh, YamlObject &yamlObject);
+
+private:
+	int ParseNextYamlObject(YamlObject &yamlObject);
+};
+
+} /* namespace libcamera */
diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
index 26912ca1..f8e18e03 100644
--- a/src/libcamera/meson.build
+++ b/src/libcamera/meson.build
@@ -46,6 +46,7 @@ libcamera_sources = files([
     'v4l2_pixelformat.cpp',
     'v4l2_subdevice.cpp',
     'v4l2_videodevice.cpp',
+    'yaml_parser.cpp',
 ])
 
 libcamera_sources += libcamera_public_headers
@@ -66,6 +67,7 @@ subdir('proxy')
 libdl = cc.find_library('dl')
 libgnutls = cc.find_library('gnutls', required : true)
 libudev = dependency('libudev', required : false)
+libyaml = dependency('yaml-0.1', required : true)
 
 if libgnutls.found()
     config_h.set('HAVE_GNUTLS', 1)
@@ -126,6 +128,7 @@ libcamera_deps = [
     libgnutls,
     liblttng,
     libudev,
+    libyaml,
 ]
 
 # We add '/' to the build_rpath as a 'safe' path to act as a boolean flag.
diff --git a/src/libcamera/yaml_parser.cpp b/src/libcamera/yaml_parser.cpp
new file mode 100644
index 00000000..203d9cf4
--- /dev/null
+++ b/src/libcamera/yaml_parser.cpp
@@ -0,0 +1,796 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2022, Google Inc.
+ *
+ * yaml_parser.cpp - libcamera yaml parsing helper
+ */
+
+#include "libcamera/internal/yaml_parser.h"
+
+#include <assert.h>
+
+#include <libcamera/base/log.h>
+
+#include <yaml.h>
+
+/**
+ * \file libcamera/internal/yaml_parser.h
+ * \brief A yaml parser helper
+ */
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(YamlParser)
+
+namespace {
+
+void setOk(bool *ok, bool result)
+{
+	if (ok)
+		*ok = result;
+}
+
+} /* namespace */
+
+/**
+ * \class YamlObject
+ * \brief A class representing the tree structure of the yaml content
+ *
+ * The YamlObject class represents the tree structure of a yaml content. A
+ * YamlObject can be a dictionary or list of YamlObjects or a value if a tree
+ * leaf.
+ */
+
+/**
+ * \fn YamlObject::isValue()
+ * \brief Return whether the YamlObject is a value
+ *
+ * \return Whether the YamlObject is a value
+ */
+
+/**
+ * \fn YamlObject::isList()
+ * \brief Return whether the YamlObject is a list
+ *
+ * \return Whether the YamlObject is a list
+ */
+
+/**
+ * \fn YamlObject::isDictionary()
+ * \brief Return whether the YamlObject is a dictionary
+ *
+ * \return Whether the YamlObject is a dictionary
+ */
+
+/**
+ * \fn YamlObject::asBool()
+ * \brief Helper function to parse the YamlObject as a bool value
+ * \param[in] defaultValue The default value when fail to parse
+ * \param[in] ok The result of whether the parse success
+ *
+ * Helper function to parse the YamlObject as a bool value.
+ *
+ * \return Value as a bool type
+ */
+bool YamlObject::asBool(bool defaultValue, bool *ok) const
+{
+	setOk(ok, false);
+
+	if (type_ != VALUE) {
+		return defaultValue;
+	}
+
+	if (value_ == "true") {
+		setOk(ok, true);
+		return true;
+	} else if (value_ == "false") {
+		setOk(ok, true);
+		return false;
+	}
+
+	return defaultValue;
+}
+
+/**
+ * \fn YamlObject::asInt32()
+ * \brief Helper function to parse the YamlObject as a int32_t value
+ * \param[in] defaultValue The default value when fail to parse
+ * \param[in] ok The result of whether the parse success
+ *
+ * Helper function to parse the YamlObject as a int32_t value.
+ *
+ * \return Value as a int32_t type
+ */
+int32_t YamlObject::asInt32(int32_t defaultValue, bool *ok) const
+{
+	setOk(ok, false);
+
+	if (type_ != VALUE)
+		return defaultValue;
+
+	int32_t value = std::strtol(value_.c_str(), nullptr, 10);
+	if (errno == ERANGE)
+		return defaultValue;
+
+	setOk(ok, true);
+	return value;
+}
+
+/**
+ * \fn YamlObject::asUint32()
+ * \brief Helper function to parse the YamlObject as a uint32_t value
+ * \param[in] defaultValue The default value when fail to parse
+ * \param[in] ok The result of whether the parse success
+ *
+ * Helper function to parse the YamlObject as a uint32_t value.
+ *
+ * \return Value as a uint32_t type
+ */
+uint32_t YamlObject::asUint32(uint32_t defaultValue, bool *ok) const
+{
+	setOk(ok, false);
+
+	if (type_ != VALUE) {
+		return defaultValue;
+	}
+
+	uint32_t value = std::strtoul(value_.c_str(), nullptr, 10);
+	if (errno == ERANGE)
+		return defaultValue;
+
+	setOk(ok, true);
+	return value;
+}
+
+/**
+ * \fn YamlObject::asDouble()
+ * \brief Helper function to parse the YamlObject as a double value
+ * \param[in] defaultValue The default value when fail to parse
+ * \param[in] ok The result of whether the parse success
+ *
+ * Helper function to parse the YamlObject as a double value.
+ *
+ * \return Value as a double type
+ */
+double YamlObject::asDouble(double defaultValue, bool *ok) const
+{
+	setOk(ok, false);
+	if (type_ != VALUE)
+		return defaultValue;
+
+	double value = std::strtod(value_.c_str(), nullptr);
+	if (errno == ERANGE)
+		return defaultValue;
+
+	setOk(ok, true);
+	return value;
+}
+
+/**
+ * \fn YamlObject::asString()
+ * \brief Helper function to parse the YamlObject as a string value
+ * \param[in] defaultValue The default value when fail to parse
+ * \param[in] ok The result of whether the parse success
+ *
+ * Helper function to parse the YamlObject as a string value.
+ *
+ * \return Value as a string type
+ */
+std::string YamlObject::asString(std::string defaultValue, bool *ok) const
+{
+	if (type_ != VALUE) {
+		setOk(ok, false);
+		return defaultValue;
+	}
+
+	return value_;
+}
+
+/**
+ * \fn YamlObject::asSize()
+ * \brief Helper function to parse the YamlObject as a Size value
+ * \param[in] defaultValue The default value when fail to parse
+ * \param[in] ok The result of whether the parse success
+ *
+ * Helper function to parse the YamlObject as a Size value. The Size structure
+ * is represented as a list of two non-negative numbers.
+ *
+ * \return Value as a Size type
+ */
+Size YamlObject::asSize(Size defaultValue, bool *ok) const
+{
+	setOk(ok, false);
+
+	if (type_ != LIST)
+		return defaultValue;
+
+	if (list_.size() != 2)
+		return defaultValue;
+
+	bool isInt = false;
+	int width = list_[0]->asUint32(0, &isInt);
+	if (!isInt)
+		return Size(0, 0);
+
+	int height = list_[1]->asUint32(0, &isInt);
+	if (!isInt)
+		return Size(0, 0);
+
+	setOk(ok, true);
+	return Size(width, height);
+}
+
+/**
+ * \fn YamlObject::length()
+ * \brief Helper function to return length of the list
+ *
+ * Helper function to parse the YamlObject as a List and return the length of
+ * the List.
+ *
+ * \return The length as a list
+ */
+int YamlObject::length() const
+{
+	assert(type_ == LIST);
+
+	return list_.size();
+}
+
+/**
+ * \fn YamlObject::operator[](int index)
+ * \brief Helper function to return an element of a list YamlObject by index
+ *
+ * Helper function to return an element of a list YamlObject by index.
+ *
+ * \return The YamlObject as an element of the list
+ */
+const YamlObject &YamlObject::operator[](int index) const
+{
+	assert(type_ == LIST);
+
+	return *list_[index];
+}
+
+/**
+ * \fn YamlObject::isMember()
+ * \brief Helper function to check if an element of a dictionary exists
+ *
+ * Helper function to return an element of a list YamlObject by index.
+ *
+ * \return Whether an element exists
+ */
+bool YamlObject::isMember(const std::string &key) const
+{
+	assert(type_ == DICTIONARY);
+
+	if (dictionary_.find(key) == dictionary_.end())
+		return false;
+
+	return true;
+}
+
+/**
+ * \fn YamlObject::getMemberNames()
+ * \brief Helper function to get all member names of the dictionary
+ *
+ * Helper function to get all member names of the dictionary.
+ *
+ * \return A vector of string as the member names
+ */
+std::vector<std::string> YamlObject::getMemberNames() const
+{
+	assert(type_ == DICTIONARY);
+
+	std::vector<std::string> memberNames;
+	for (auto &[key, _] : dictionary_)
+		memberNames.push_back(key);
+
+	return memberNames;
+}
+
+/**
+ * \fn YamlObject::get()
+ * \brief Helper function to get a member by name from the dictionary
+ *
+ * Helper function to get a member by name from the dictionary
+ *
+ * \return A YamlObject as a member
+ */
+const YamlObject &YamlObject::get(const std::string &key) const
+{
+	assert(type_ == DICTIONARY);
+	assert(isMember(key));
+
+	auto iter = dictionary_.find(key);
+	return *iter->second;
+}
+
+class YamlParser::Private : public Extensible::Private
+{
+	LIBCAMERA_DECLARE_PUBLIC(YamlParser)
+
+public:
+	Private();
+	~Private();
+
+	using ItemParser = const std::function<int()> &;
+	using RecordParser = const std::function<int(const std::string &)> &;
+
+	int initParser(FILE *fh);
+	void release();
+
+	int consumeDocumentStart();
+	int consumeDocumentEnd();
+
+	int parseString(std::string &value);
+	int parseList(ItemParser parseItem);
+	int parseDictionary(RecordParser parseRecord);
+
+private:
+	int nextEvent(yaml_event_t &event);
+	int consume(yaml_event_type_t);
+
+	int parseDictionaryOrList(bool isDictionary,
+				  const std::function<int()> &parseItem);
+
+	bool nextEventLoaded_;
+	yaml_event_t nextEvent_;
+
+	bool parserValid_;
+	yaml_parser_t parser_;
+};
+
+/**
+ * \class YamlParser::Private
+ * \brief A Private class helps control event based parsing for yaml files.
+ *
+ * The YamlParser::Private class stores the internal yaml_parser_t and provides
+ * helper functions to do event based parsing for yaml files.
+ */
+YamlParser::Private::Private()
+	: nextEventLoaded_(false), parserValid_(false)
+{
+}
+
+/**
+ * \class YamlParser::Private
+ * \brief Destructor of YamlParser::Private.
+ */
+YamlParser::Private::~Private()
+{
+	release();
+}
+
+/**
+ * \fn YamlParser::Private::initParser()
+ * \brief Initialize a parser with an opened file for parsing
+ * \param[in] fh The yaml file to parse
+ *
+ * Prior to parsing the yaml content, the YamlParser must be initialized with
+ * an opened FILE to create an internal parser. The FILE need to stay valid
+ * during the process. The release() should be called after use.
+ *
+ * \return 0 on success or a negative error code otherwise
+ * \retval -EINVAL The parser is failed to initialize
+ */
+int YamlParser::Private::initParser(FILE *fh)
+{
+	/* yaml_parser_initialize returns 1 when succeeded */
+	if (!yaml_parser_initialize(&parser_)) {
+		LOG(YamlParser, Error) << "Failed to initialize yaml parser";
+		return -EINVAL;
+	}
+	parserValid_ = true;
+	yaml_parser_set_input_file(&parser_, fh);
+
+	return 0;
+}
+
+/**
+ * \fn YamlParser::Private::release()
+ * \brief Release the internal parser
+ *
+ * After parsing the yaml content, The YamlParser::release() should be called
+ * to release the internal parser.
+ */
+void YamlParser::Private::release()
+{
+	if (nextEventLoaded_) {
+		yaml_event_delete(&nextEvent_);
+		nextEventLoaded_ = false;
+	}
+
+	if (parserValid_) {
+		parserValid_ = false;
+		yaml_parser_delete(&parser_);
+	}
+}
+
+/**
+ * \fn YamlParser::Private::consume()
+ * \brief Cosume the next event with an expected event type
+ *
+ * \param[in] type The expected event type to consume
+ *
+ * Consume next event and check whether next event has an expected type.
+ *
+ * \return 0 on success or a negative error code otherwise
+ * \retval -EINVAL The parser is failed to initialize
+ */
+int YamlParser::Private::consume(yaml_event_type_t type)
+{
+	yaml_event_t event;
+	int ret = nextEvent(event);
+	if (ret)
+		return ret;
+
+	if (event.type != type) {
+		LOG(YamlParser, Error)
+			<< "Expect event: " << type
+			<< " but get " << event.type
+			<< " at line: " << event.start_mark.line
+			<< " column: " << event.start_mark.column;
+		return -EINVAL;
+	}
+
+	yaml_event_delete(&event);
+	nextEventLoaded_ = false;
+
+	return 0;
+}
+
+/**
+ * \typedef YamlParser::Private::ItemParser
+ * \brief The functor to handle an item in a yaml list
+ */
+
+/**
+ * \typedef YamlParser::Private::RecordParser
+ * \brief The functor to handle an item in a yaml dictionary
+ */
+
+/**
+ * \fn YamlParser::Private::consumeDocumentStart()
+ * \brief Consume start of a yaml document
+ *
+ * Check yaml start of a yaml document. The function should be called and
+ * checked before parsing any content of the yaml file.
+ *
+ * \return 0 on success or a negative error code otherwise
+ * \retval -EINVAL The parser is failed to validate start of a yaml file
+ */
+int YamlParser::Private::consumeDocumentStart()
+{
+	if (consume(YAML_STREAM_START_EVENT))
+		return -EINVAL;
+
+	if (consume(YAML_DOCUMENT_START_EVENT))
+		return -EINVAL;
+
+	return 0;
+}
+
+/**
+ * \fn YamlParser::Private::consumeDocumentEnd()
+ * \brief Consume end of a yaml document
+ *
+ * Check yaml end of a yaml document. The function should be called and
+ * checked after parsing any content of the yaml file.
+ *
+ * \return 0 on success or a negative error code otherwise
+ * \retval -EINVAL The parser is failed to validate end of a yaml file
+ */
+int YamlParser::Private::consumeDocumentEnd()
+{
+	if (consume(YAML_DOCUMENT_END_EVENT))
+		return -EINVAL;
+
+	if (consume(YAML_STREAM_END_EVENT))
+		return -EINVAL;
+
+	return 0;
+}
+
+/**
+ * \fn YamlParser::Private::parseString()
+ * \brief Parse scalar and read its content as a string
+ * \param[in] value The string reference to fill value
+ *
+ * A helper function to peek the next event, check whether it's scalar type
+ * and read its content as a string.
+ *
+ * \return 0 on success or a negative error code otherwise
+ * \retval -EINVAL The parser is failed to initialize
+ */
+int YamlParser::Private::parseString(std::string &value)
+{
+	yaml_event_t event;
+	int ret = nextEvent(event);
+	if (ret)
+		return ret;
+
+	if (event.type != YAML_SCALAR_EVENT) {
+		LOG(YamlParser, Error) << "Expect scalar at line: "
+				       << event.start_mark.line
+				       << " column: "
+				       << event.start_mark.column;
+		return -EINVAL;
+	}
+
+	value.assign(reinterpret_cast<char *>(event.data.scalar.value),
+		     event.data.scalar.length);
+
+	consume(YAML_SCALAR_EVENT);
+
+	return 0;
+}
+
+/**
+ * \fn YamlParser::Private::parseList()
+ * \brief Parse a list with an callback function to parse each item in the list
+ * \param[in] parseItem The functor to parse a single item in the list
+ *
+ * Start to parse a list from the current position and call back to parseItem
+ * for parsing each item in the list. The parseItem should return 0 on success
+ * or a negative error code otherwise.
+ *
+ * \return 0 on success or a negative error code otherwise
+ * \retval -EINVAL The parser is failed to parse the list
+ */
+int YamlParser::Private::parseList(ItemParser parseItem)
+{
+	return parseDictionaryOrList(false, parseItem);
+}
+
+/**
+ * \fn YamlParser::Private::parseDictionary()
+ * \brief Parse a dictionary with an callback function to parse each item
+ * \param[in] parseRecord The functor to parse a single item in the dictionary
+ *
+ * Start to parse a dictionary from the current position and call back to
+ * parseRecord to parse value by a string argument as the key to the value.
+ * The parseRecord should return 0 on success or a negative error code
+ * otherwise.
+ *
+ * \return 0 on success or a negative error code otherwise
+ * \retval -EINVAL The parser is failed to parse the dictionary
+ */
+int YamlParser::Private::parseDictionary(RecordParser parseRecord)
+{
+	auto parseItem = [this, &parseRecord]() {
+		std::string key;
+		int ret = parseString(key);
+		if (ret)
+			return -EINVAL;
+
+		return parseRecord(key);
+	};
+
+	return parseDictionaryOrList(true, parseItem);
+}
+
+/**
+ * \fn YamlParser::Private::parseDictionaryOrList()
+ * \brief A helper function to abstract common part of parsing dictionary or list
+ *
+ * \param[in] isDictionary True for parsing a dictionary, and false for a list
+ * \param[in] parseItem The callback to handle an item
+ *
+ * A helper function to abstract parsing a item from a dictionary or a list.
+ * The differences of them in a yaml event stream are:
+ *
+ * 1. The start and end event type are different
+ * 2. There is a leading scalar string as key in the items of a dictionary
+ *
+ * The caller should handle the leading key string in its callback parseItem
+ * when it's a dictionary.
+ *
+ * \return 0 on success or a negative error code otherwise
+ * \retval -EINVAL The parser is failed to initialize
+ */
+int YamlParser::Private::parseDictionaryOrList(bool isDictionary,
+					       const std::function<int()> &parseItem)
+{
+	yaml_event_type_t startEventType = YAML_SEQUENCE_START_EVENT;
+	yaml_event_type_t endEventType = YAML_SEQUENCE_END_EVENT;
+
+	if (isDictionary) {
+		startEventType = YAML_MAPPING_START_EVENT;
+		endEventType = YAML_MAPPING_END_EVENT;
+	}
+
+	if (consume(startEventType))
+		return -EINVAL;
+
+	/*
+	 * Add a safety counter to make sure we don't loop indefinitely in case
+	 * the configuration file is malformed.
+	 */
+	unsigned int sentinel = 1000;
+	yaml_event_t event;
+	do {
+		int ret = nextEvent(event);
+		if (ret)
+			return ret;
+
+		if (event.type == endEventType)
+			return consume(endEventType);
+
+		ret = parseItem();
+		if (ret)
+			return ret;
+
+		--sentinel;
+	} while (sentinel);
+
+	if (!sentinel)
+		return -EINVAL;
+
+	return 0;
+}
+
+/**
+ * \fn YamlParser::Private::nextEvent()
+ * \brief Peek the next event
+ *
+ * \param[in] event The event reference to fill information
+ *
+ * Peek the next event in the current yaml event stream, and return -EINVAL when
+ * there is no more event.
+ *
+ * \return 0 on success or a negative error code otherwise
+ * \retval -EINVAL The parser is failed to initialize
+ */
+int YamlParser::Private::nextEvent(yaml_event_t &event)
+{
+	if (nextEventLoaded_) {
+		event = nextEvent_;
+		return 0;
+	}
+
+	/* yaml_parser_parse returns 1 when succeeded */
+	if (1 != yaml_parser_parse(&parser_, &nextEvent_)) {
+		return -EINVAL;
+	}
+
+	nextEventLoaded_ = true;
+	event = nextEvent_;
+
+	return 0;
+}
+
+/**
+ * \class YamlParser
+ * \brief A helper class for parsing yaml files.
+ *
+ * The YamlParser class eases handling of parsing a yaml file by providing
+ * helper function to extract content yaml files into a tree base yaml object.
+ *
+ * Example usage 1:
+ * The following code illustrates how to parse the following yaml file:
+ *
+ * name:
+ * 	"John"
+ * numbers:
+ * 	- 1
+ * 	- 2
+ *
+ * @code
+ *
+ *	YamlObject root;
+ *	YamlParser yamlParser;
+ *	if (yamlParser.ParseAsYamlObject(fh, root));
+ *		return;
+ *
+ *	if (!root.isDictionary())
+ *		return;
+ *
+ *	std::string name = root.get("name");
+ *	cout << name.asString();
+ *
+ *	const YamlObject &numbers = root.get("numbers");
+ *	if (!numbers.isList())
+ *		return;
+ *
+ *	for (int i = 0; i < numbers.size; i++)
+ *		cout << numbers[i].asInt();
+ *
+ * @endcode
+ *
+ * Function ParseAsYamlObject(FILE *, YamlObject &) accept an opened FILE and
+ * initialize an internal parser. The FILE need to stay valid during function
+ * call.
+ */
+
+/**
+ * \brief Construct a YamlParser
+ */
+YamlParser::YamlParser()
+	: Extensible(std::make_unique<Private>())
+{
+}
+
+/**
+ * \fn YamlParser::ParseAsYamlObject()
+ * \brief Parse a yaml file as a YamlObject
+ * \param[in] fh The yaml file to parse
+ * \param[in] yamlObject The result fo YamlObject
+ *
+ * Prior to parsing the yaml content, the function accepts an opened FILE to
+ * create an internal parser. The FILE need to stay valid during the function
+ * call. When fails, the contect of the YamlObject is undefined.
+ *
+ * \return 0 on success or a negative error code otherwise
+ * \retval -EINVAL Fail to parse the yaml file.
+ */
+int YamlParser::ParseAsYamlObject(FILE *fh, YamlObject &yamlObject)
+{
+	if (_d()->initParser(fh))
+		return -EINVAL;
+
+	if (_d()->consumeDocumentStart())
+		goto error;
+
+	if (ParseNextYamlObject(yamlObject))
+		goto error;
+
+	if (_d()->consumeDocumentEnd())
+		goto error;
+
+	_d()->release();
+	return 0;
+
+error:
+	_d()->release();
+	return -EINVAL;
+}
+
+/**
+ * \fn YamlParser::ParseNextEvent()
+ * \brief Helper function to parse next yaml event and read it as a YamlObject
+ * \param[in] yamlObject The result of YamlObject
+ *
+ * A helper function to parse next yaml event by peeking next event and parse
+ * them separately as a value, list or dictionary.
+ *
+ * \return 0 on success or a negative error code otherwise
+ * \retval -EINVAL Fail to parse the yaml file.
+ */
+int YamlParser::ParseNextYamlObject(YamlObject &yamlObject)
+{
+	yaml_event_t event;
+
+	if (_d()->nextEvent(event))
+		return -EINVAL;
+
+	if (event.type == YAML_SCALAR_EVENT) {
+		yamlObject.type_ = YamlObject::VALUE;
+		_d()->parseString(yamlObject.value_);
+		return 0;
+	}
+
+	if (event.type == YAML_SEQUENCE_START_EVENT) {
+		yamlObject.type_ = YamlObject::LIST;
+		auto &list = yamlObject.list_;
+		auto handler = [this, &list]() {
+			list.emplace_back(new YamlObject());
+			return ParseNextYamlObject(*list.back());
+		};
+		return _d()->parseList(handler);
+	}
+
+	if (event.type == YAML_MAPPING_START_EVENT) {
+		yamlObject.type_ = YamlObject::DICTIONARY;
+		auto &dictionary = yamlObject.dictionary_;
+		auto handler = [this, &dictionary](const std::string &key) {
+			dictionary[key].reset(new YamlObject());
+			return ParseNextYamlObject(*dictionary[key]);
+		};
+		return _d()->parseDictionary(handler);
+	}
+
+	LOG(YamlParser, Error) << "Invalid yaml file";
+	return -EINVAL;
+}
+
+} /* namespace libcamera */
-- 
2.35.0.263.gb82422642f-goog



More information about the libcamera-devel mailing list