My JSON is this:
{
"apps":[
{
"id":"x",
"val":"y",
}
]
}
I can get id's value "x" by looping, and exiting when it.first is id:
for (const ptree::value_type &app : root.get_child("apps"))
{
for (const ptree::value_type &it : app.second) {
if (it.first == "id") {
std::cout << it.second.get_value<std::string>().c_str() << std::endl;
}
}
}
What I want, however, is to get the id's value by something like this:
std::cout << root.get<std::string>("apps[0].id").c_str() << std::endl;
Of course this displays nothing to me, for I am probably using wrong syntax accessing 1st element of the apps array. It might be that this has to be done in a different way all together.
I have found only this ugly and dangerous method:
std::cout << root.get_child("apps").begin()->second.begin()->second.get_value<std::string>().c_str() << std::endl;
I cannot really use it this way as it won't throw an exception when the array is empty, it will core dump!
Below is the whole program to make it easier for any one who wants to help:
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
using boost::property_tree::ptree;
int main()
{
std::stringstream ss("{\"apps\":[{\"id\":\"x\",\"val\":\"y\"}]}");
ptree root;
read_json(ss, root);
for (const ptree::value_type &app : root.get_child("apps"))
{
for (const ptree::value_type &it : app.second) {
if (it.first == "id") {
std::cout << it.second.get_value<std::string>().c_str() << std::endl;
}
}
}
std::cout << root.get_child("apps").begin()->second.begin()->second.get_value<std::string>().c_str() << std::endl;
return 0;
}
As the docs say, array elements are nodes with "" keys.
If you're after the first element, you're in luck:
root.get("apps..id", "")
The .. in the path selects the first empty key
Live On Coliru
#include <boost/property_tree/json_parser.hpp>
#include <iostream>
using boost::property_tree::ptree;
int main() {
std::stringstream ss(R"({"apps":[{"id":"x","val":"y"}]})");
ptree root;
read_json(ss, root);
std::cout << root.get("apps..id", "") << "\n";
}
BONUS
If you need to address elements other than the first, write a helper function. This would be a good start:
#include <string>
#include <stdexcept> // std::out_of_range
template <typename Tree>
Tree query(Tree& pt, typename Tree::path_type path) {
if (path.empty())
return pt;
auto const head = path.reduce();
auto subscript = head.find('[');
auto name = head.substr(0, subscript);
auto index = std::string::npos != subscript && head.back() == ']'
? std::stoul(head.substr(subscript+1))
: 0u;
auto matches = pt.equal_range(name);
if (matches.first==matches.second)
throw std::out_of_range("name:" + name);
for (; matches.first != matches.second && index; --index)
++matches.first;
if (index || matches.first==matches.second)
throw std::out_of_range("index:" + head);
return query(matches.first->second, path);
}
Here's some live tests using it:
Live On Coliru
#include <boost/property_tree/json_parser.hpp>
#include <iostream>
using boost::property_tree::ptree;
int main() {
std::stringstream ss(R"({
"apps": [
{
"id": "x",
"val": "y",
"id": "hidden duplicate"
},
{
"id": "a",
"val": "b"
}
]
})");
ptree root;
read_json(ss, root);
for (auto path : {
"apps..id", "apps.[0].id", // (equivalent)
//
"apps.[0].id[]", // invalid
"apps.[0].id[0]", // x
"apps.[0].id[1]", // hidden duplicate
"apps.[1].id", // a
"apps.[1].id[0]", // a
"apps.[1].id[1]", // out of range
"apps.[2].id", // out of range
"drinks" // huh, no drinks at the foo bar
}) try {
std::cout << "Path '" << path << "' -> ";
std::cout << query(root, path).get_value<std::string>() << "\n";
} catch(std::exception const& e) {
std::cout << "Error: " << e.what() << "\n";
}
}
Prints:
Path 'apps..id' -> x
Path 'apps.[0].id' -> x
Path 'apps.[0].id[]' -> Error: stoul
Path 'apps.[0].id[0]' -> x
Path 'apps.[0].id[1]' -> hidden duplicate
Path 'apps.[1].id' -> a
Path 'apps.[1].id[0]' -> a
Path 'apps.[1].id[1]' -> Error: index:id[1]
Path 'apps.[2].id' -> Error: index:[2]
Path 'drinks' -> Error: name:drinks
Related
In this convoluted example, two for loops are started by boost::asio::spawn() asynchronously. The first for loop prints an odd number every 1000us and the second one prints an even number every 1000us.
I expect the output to be something like 1 2 3 4 5 6 and then the 'Throw an error' message should be printed to stderr by the call to cerr.
However, the exception is actually thrown in loop.run() so it is not caught by the try catch block.
Can someone point out how to properly catch the runtime_error when calling spawn()? Does spawn() not reraise the exception thrown in the spawned coroutine to its parent scope?
using namespace std;
using namespace boost::asio;
using boost::posix_time::microseconds;
io_service loop;
spawn(loop, [&loop](yield_context yield)
{
try
{
spawn(loop, [&loop](yield_context yield)
{
deadline_timer timer{loop};
for(unsigned i = 0; i < 3; ++i)
{
cout << i * 2 + 1 << endl;
timer.expires_from_now(microseconds(1000));
timer.async_wait(yield);
}
throw runtime_error("Throw an error");
});
spawn(loop, [&loop](yield_context yield)
{
deadline_timer timer{loop};
for(unsigned i = 0; i < 3; ++i)
{
cout << (i + 1) * 2 << endl;
timer.expires_from_now(microseconds(1000));
timer.async_wait(yield);
}
});
} catch(const runtime_error& ex)
{
cerr << ex.what() << endl;
}
});
loop.run();
All handlers are invoked by the service loop, meaning you always need to handle errors: Should the exception thrown by boost::asio::io_service::run() be caught?
Sidenote: In the case of coroutines, it's my experience that catching by reference is a bit tricky. This more than likely has to do with the lifetime of the coroutine stack itself.
You can demonstrate it using:
while (true) {
try {
loop.run();
break;
} catch(std::runtime_error ex) {
std::cerr << "L:" << __LINE__ << ": " << ex.what() << "\n";
}
}
Other Notes
Note that passing error_code errors can be done in two ways across idiomatic Asio interfaces, including Coroutines: How to set error_code to asio::yield_context
Full Sample
Simply adapting your sample to be selfcontained:
Live On Coliru
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/high_resolution_timer.hpp>
using namespace std::chrono_literals;
int main() {
boost::asio::io_service loop;
spawn(loop, [&loop](boost::asio::yield_context yield)
{
try
{
spawn(yield, [&loop](boost::asio::yield_context yield)
{
boost::asio::high_resolution_timer timer{loop};
for(unsigned i = 0; i < 3; ++i)
{
std::cout << i * 2 + 1 << std::endl;
timer.expires_from_now(100ms);
timer.async_wait(yield);
}
throw std::system_error(ENOENT, std::system_category(), "Throw an error");
//throw boost::system::system_error(ENOENT, boost::system::system_category(), "Throw an error");
});
spawn(yield, [&loop](boost::asio::yield_context yield)
{
boost::asio::high_resolution_timer timer{loop};
for(unsigned i = 0; i < 3; ++i)
{
std::cout << (i + 1) * 2 << std::endl;
timer.expires_from_now(100ms);
timer.async_wait(yield);
}
});
} catch(const std::runtime_error& ex)
{
std::cerr << "L:" << __LINE__ << ": " << ex.what() << "\n";
}
});
while (true) {
try {
loop.run();
break;
} catch(std::runtime_error ex) {
std::cerr << "L:" << __LINE__ << ": " << ex.what() << "\n";
}
}
}
Prints
1
2
3
4
5
6
L:49: Throw an error: No such file or directory
BONUS:
Doing the extra approach with generalized competion token and async_result:
Live On Coliru
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/high_resolution_timer.hpp>
using namespace std::chrono_literals;
boost::asio::io_service loop;
template <typename Token>
auto async_foo(bool success, Token&& token)
{
typename boost::asio::handler_type<Token, void(boost::system::error_code, int)>::type
handler (std::forward<Token> (token));
boost::asio::async_result<decltype (handler)> result (handler);
boost::asio::yield_context yield(token);
boost::asio::high_resolution_timer timer{loop};
for(unsigned i = 0; i < 3; ++i) {
std::cout << (i * 2 + (success?0:1)) << std::endl;
timer.expires_from_now(100ms);
timer.async_wait(yield);
}
if (success)
handler(42);
else
throw boost::system::system_error(ENOENT, boost::system::system_category(), "Throw an error");
return result.get();
}
int main() {
auto spawn_foo = [](bool success) {
spawn(loop, [=](boost::asio::yield_context yield) {
try
{
int answer = async_foo(success, yield);
std::cout << "async_foo returned " << answer << std::endl;
} catch(const std::runtime_error& ex)
{
std::cerr << "L:" << __LINE__ << ": " << ex.what() << std::endl;
}
});
};
spawn_foo(true);
spawn_foo(false);
loop.run();
}
Prints
0
1
2
3
4
5
async_foo returned 42
L:45: Throw an error: No such file or directory
current_user pointer works within functions of System.cpp, but resets afterwards. The current_user seems to be updated locally when it's set within the function createUser(string name) when it's called in the switch statement. However, when I try to call it outside the function it seems to have not been updated at all. Not entirely sure what's going on.
Main.cpp
#include <iostream>
#include "System.h"
#include "User.h"
using namespace std;
int main()
{
System s;
s.run();
// Works
User a("Test");
User b("Test 2");
User c("Test 3");
User* current = &a;
cout << "The current user is: " << current->getName() << endl;
current = &b;
cout << "Now it's: " << current->getName() << endl;
current = &c;
cout << "The final current user is: " << current->getName() << endl;
// Does not work
cout << "Current user: " << s.getCurrentUser()->getName() << endl;
}
System.h
#ifndef SYSTEM_H
#define SYSTEM_H
#include "User.h"
#include "Group.h"
#include "MessageBuffer.h"
#include "Banner.h"
#include <iostream>
#include <vector>
class System
{
public:
System();
char validInput(std::string inputIn);
bool validUsername(std::string nameIn);
bool userExists(std::string nameIn);
void createUser(std::string nameIn);
void run();
User* getCurrentUser();
private:
User* current_user;
std::vector<User> user_list;
std::vector<Group> group_list;
};
#endif // SYSTEM_H
System.cpp
// Program 1: TigerBook Social Network
// File: System.cpp
// Description: Class Implimentation of the System Class. Instantiates objects that must be initialized and handles
// basic user screw-ups (choosing and option out of bounds).
#include <iostream>
#include "System.h"
using namespace std;
// Function: Default System Constructor
// Inputs: None
// Description: Default constructor for the class.
System::System()
{
}
User* System::getCurrentUser()
{
return current_user;
}
// Function: validInput
// Inputs: string inputIn
// Outputs: char value of input at 0
// Description: Determines whether the input is valid.
char System::validInput(string inputIn)
{
if (inputIn.length() == 1)
{
return inputIn[0];
}
else
{
return '0';
}
}
// Function: validUsername
// Inputs: string username
// Outputs: true if valid, false if not
// Description: Determines whether the username is valid
bool System::validUsername(string nameIn)
{
if (nameIn.empty() || nameIn.length() < 2 || (nameIn.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") != string::npos))
{
cerr << "\n\t*** ERROR: Invalid user name, please try again! ***" << endl;
return false;
}
else
{
return true;
}
}
// Function: userExists
// Inputs: string username
// Outputs: true if exists, false if not
// Description: Determines whether the username exists in user_list.
bool System::userExists(string nameIn)
{
return false;
}
// Function: createUser
// Inputs: string username
// Outputs: void
// Description: Creates new user and adds it to user_list.
void System::createUser(string nameIn)
{
User u(nameIn);
user_list.push_back(u);
current_user = &u;
}
// Function: run
// Inputs: None
// Outputs: void
// Description: Program driver, handles basic user input and screw-ups
void System::run()
{
//current_user = NULL;
Banner banner("The TigerBook Social Network!");
cout << banner.getBanner() << endl;
bool quit = false;
string input;
while(!quit)
{
cout << "\nCreate new user (n), Broadcast (b), Multicast (m), Unicast (u), Wall page (w), Home page (h), Create new group (g), " << endl;
cout << "Join a group (j), Switch user (s), Quit (q)\n" << endl;
cout << "Choose an option: ";
getline(cin, input);
if (current_user == NULL && (input != "N" && input != "n" && input != "Q" && input != "q"))
{
cerr << "\n\t*** ERROR: There is no current user, please create a new user! ***" << endl;
continue;
}
switch (validInput(input))
{
// Create new user
case 'N':
case 'n':
{
string username;
cout << "\nPlease enter user name: ";
getline(cin, username);
if (!validUsername(username))
{
continue;
}
else if (userExists(username))
{
cerr << "\n\t*** ERROR: The user \"" + username + "\" already exists, please try again! ***" << endl;
continue;
}
else
{
createUser(username);
cout << "\nCurrent user: " << getCurrentUser()->getName() << endl; // test current_user
}
break;
}
case 'B':
case 'b':
{
break;
}
case 'M':
case 'm':
{
break;
}
case 'U':
case 'u':
{
break;
}
case 'W':
case 'w':
{
break;
}
case 'H':
case 'h':
{
break;
}
case 'G':
case 'g':
{
break;
}
case 'J':
case 'j':
{
break;
}
case 'S':
case 's':
{
break;
}
case 'Q':
case 'q':
{
quit = true;
banner.setBanner("Thank you for using TigerBook Social Network!");
cout << banner.getBanner() << endl << endl;
break;
}
default:
{
cerr << "\n\t*** ERROR: Invalid input, please try again! ***" << endl;
}
} // End of switch statement
} // End of loop
}
User.h
#ifndef USER_H
#define USER_H
#include <string>
class User
{
public:
User();
User(std::string nameIn);
std::string getName();
void setName(std::string nameIn);
private:
std::string name;
};
#endif // USER_H
User.cpp
// Program 1: TigerBook Social Network
// File: User.cpp
// Description: Class implementation for User class
#include "User.h"
using namespace std;
// Constructor (Default)
User::User()
{
//ctor
}
// Constructor
// Inputs: string that sets name
// Description: Constructs user object and assigns its name.
User::User(string nameIn)
{
name = nameIn;
}
// Function: setName
// Inputs: string that sets name
// Outputs: void
// Description: Sets name of user object.
void User::setName(string nameIn)
{
name = nameIn;
}
// Function: getName
// Inputs: none
// Outputs: Name of user
// Description: Returns the name of the user object.
string User::getName()
{
return name;
}
You have:
void System::createUser(string nameIn)
{
User u(nameIn);
user_list.push_back(u);
current_user = &u;
}
Here, you are storing pointer to a local variable. The pointer becomes a dangling pointer as soon as the function returns. Any use of current_usr to access the object after the function returns is cause for undefined behavior.
You can use:
void System::createUser(string nameIn)
{
User u(nameIn);
user_list.push_back(u);
current_user = &(user_list.back());
}
Even that is very fragile. It will work if you carefully manage the objects in user_list.
A better option will be not to store a pointer to the object at all. You can, for example, use:
User* System::getCurrentUser()
{
if ( user_list.empty() )
{
return nullptr;
}
return &(user_list.back());
}
So I just noticed something weird about opening .txt files in sublime that were created in vim.
It seems sublime adds an empty line at the end of the .txt file. For example, if I use vim/gedit to type the following 2 line file:
1
2
When I open this .txt file in Sublime, it opens as
1
2
~
where I have used "~" to represent an empty line. Could someone try this and tell me if it's the same for you?
This is the code I was using that doesn't seem to work when I create the .txt file in sublime:
#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
int main()
{
int a;
int b;
ifstream inFile;
bool validInputFound;
inFile.open("inputVals.txt");
if (inFile.fail())
{
cout << "Unable to open input file!" << endl;
exit(1);
}
validInputFound = false;
while (!validInputFound)
{
inFile >> a;
if (inFile.eof())
{
cout << "EOF before reading a" << endl;
exit(2);
}
else if (inFile.fail())
{
inFile.clear();
inFile.ignore(200, '\n');
}
else
{
validInputFound = true;
}
}
cout << "Read a: " << a << endl;
validInputFound = false;
while (!validInputFound)
{
inFile >> b;
if (inFile.eof())
{
cout << "EOF before reading b" << endl;
exit(2);
}
else if (inFile.fail())
{
inFile.clear();
inFile.ignore(200, '\n');
}
else
{
validInputFound = true;
}
}
cout << "Read b: " << b << endl;
cout << "Sum: " << a + b << endl;
inFile.close();
return (0);
}
The expected output is:
Read a: 1
Read b: 2
Sum: 3
But if you create the inputVals.txt file in Sublime, you obtain:
Read a: 1
EOF before reading b
It looks like vim adds the newline and not the Sublime Text.
As to why: Why would Vim add a new line at the end of a file?
I have been trying for too much time now, to access a json_reader ptree from the boost library.
I have a json file that is capsulated really often: (pseudo-json:)
"Foo": {
"nameofFoo:"foofoo"
"Bar": [{
"BarFoo":
{ BarFooDeep: {
BarFooDeepDeep: {
"BarFooValue1": 123
"BarFooValue2" : 456
}
}
}
"FooBar": [ {
"FooBarDeep" :[ {
FooBarDeepDeep:[ {
FooBarValue1: "ineedthis"
FooBarValue2: "andthis"
} ]
FooBarDeepDeep1:[ {
FooBarValue1: "ineedthis"
FooBarValue2: "andthis"
} ]
"FooBarDeep" :[ {
FooBarDeepDeep2:[ {
FooBarValue1: "ineedthis"
FooBarValue2: "andthis"
} ]
FooBarDeepDeep3:[ {
FooBarValue1: "ineedthis"
FooBarValue2: "andthis"
} ]
and so on .... won t complete this now...
Now I only need to get FooBarValue1 and FooBarValue2 of all FooBar.
I know ptree puts arrays together with empty childs ("")
I can access all the members by itereration over all the childs recursively.
But is there not a better way to access special values?
how does ptree find works? i always get compiler errors ...
ptree jsonPT;
read_json( JSON_PATH, jsonPT);
ptree::const_iterator myIT = jsonPT.find("FooBarValue1");
double mlat = boost::lexical_cast<int>(myIT->second.data());
error: conversion from
‘boost::property_tree::basic_ptree,
std::basic_string >::assoc_iterator’ to non-scalar type
‘boost::property_tree::basic_ptree,
std::basic_string >::const_iterator’ requested
ptree::const_iterator myIT = jsonPT.find("FooBarValue1");
Can anyone give me a useful hint how to get access to this ptree?!?
As hinted in the linked answer I commented (Boost.PropertyTree subpath processing), you could write your own "selector" query, so you could write stuff like:
read_json("input.txt", pt);
std::ostream_iterator<std::string> out(std::cout, ", ");
std::cout << "\nSpecific children but in arrays: ";
enumerate_path(pt, "Foo.Bar..FooBar..FooBarDeep1..FooBarDeepDeep6..FooBarValue2", out);
std::cout << "\nSingle wildcard: ";
enumerate_path(pt, "Foo.Bar..FooBar..FooBarDeep1..*..FooBarValue2", out);
std::cout << "\nTwo wildcards: ";
enumerate_path(pt, "Foo.Bar..FooBar..*..*..FooBarValue2", out);
The enumerate_path function need not be too complicated and takes any output iterator (so you can back_inserter(some_vector) just as well):
template <typename Tree, typename Out, typename T = std::string>
Out enumerate_path(Tree const& pt, typename Tree::path_type path, Out out) {
if (path.empty())
return out;
if (path.single()) {
*out++ = pt.template get<T>(path);
} else {
auto head = path.reduce();
for (auto& child : pt) {
if (head == "*" || child.first == head) {
out = enumerate_path(child.second, path, out);
}
}
}
return out;
}
As simple working demo prints:
Specific children but in arrays: andthis6,
Single wildcard: andthis6, andthis7, andthis8, andthis9,
Two wildcards: andthis1, andthis2, andthis3, andthis4, andthis6, andthis7, andthis8, andthis9,
That is with the following input.txt:
{
"Foo": {
"nameofFoo": "foofoo",
"Bar": [{
"BarFoo": {
"BarFooDeep": {
"BarFooDeepDeep": {
"BarFooValue1": 123,
"BarFooValue2": 456
}
}
},
"FooBar": [{
"FooBarDeep0": [{
"FooBarDeepDeep1": [{
"FooBarValue1": "ineedthis1",
"FooBarValue2": "andthis1"
}],
"FooBarDeepDeep2": [{
"FooBarValue1": "ineedthis2",
"FooBarValue2": "andthis2"
}]
},
{
"FooBarDeepDeep3": [{
"FooBarValue1": "ineedthis3",
"FooBarValue2": "andthis3"
}],
"FooBarDeepDeep4": [{
"FooBarValue1": "ineedthis4",
"FooBarValue2": "andthis4"
}]
}],
"FooBarDeep1": [{
"FooBarDeepDeep6": [{
"FooBarValue1": "ineedthis6",
"FooBarValue2": "andthis6"
}],
"FooBarDeepDeep7": [{
"FooBarValue1": "ineedthis7",
"FooBarValue2": "andthis7"
}]
},
{
"FooBarDeepDeep8": [{
"FooBarValue1": "ineedthis8",
"FooBarValue2": "andthis8"
}],
"FooBarDeepDeep9": [{
"FooBarValue1": "ineedthis9",
"FooBarValue2": "andthis9"
}]
}]
}]
}]
}
}
Live On Coliru
Full Listing
Live On Coliru
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <iostream>
template <typename Tree, typename Out, typename T = std::string>
Out enumerate_path(Tree const& pt, typename Tree::path_type path, Out out) {
if (path.empty())
return out;
if (path.single()) {
*out++ = pt.template get<T>(path);
} else {
auto head = path.reduce();
for (auto& child : pt) {
if (head == "*" || child.first == head) {
out = enumerate_path(child.second, path, out);
}
}
}
return out;
}
int main() {
std::ostream_iterator<std::string> out(std::cout, ", ");
using namespace boost::property_tree;
ptree pt;
read_json("input.txt", pt);
std::cout << "\nSpecific children but in arrays: ";
enumerate_path(pt, "Foo.Bar..FooBar..FooBarDeep1..FooBarDeepDeep6..FooBarValue2", out);
std::cout << "\nSingle wildcard: ";
enumerate_path(pt, "Foo.Bar..FooBar..FooBarDeep1..*..FooBarValue2", out);
std::cout << "\nTwo wildcards: ";
enumerate_path(pt, "Foo.Bar..FooBar..*..*..FooBarValue2", out);
}
find() is for retrieving a child node by key; it doesn't search the whole ptree. It returns an assoc_iterator (or const_assoc_iterator), which you can convert to an iterator via the to_iterator() method on the parent:
ptree::const_assoc_iterator assoc = jsonPT.find("FooBarValue1");
ptree::const_iterator myIT = jsonPT.to_iterator(assoc);
To search the ptree, you'll need to iterate it recursively:
struct Searcher {
struct Path { std::string const& key; Path const* prev; };
void operator()(ptree const& node, Path const* path = nullptr) const {
auto it = node.find("FooBarValue1");
if (it == node.not_found()) {
for (auto const& child : node) { // depth-first search
Path next{child.first, path};
(*this)(child.second, &next);
}
} else { // found "FooBarValue1"
double mlat = boost::lexical_cast<int>(myIT->second.data());
// ...
std::cout << "Mlat: " << mlat << std::endl;
std::cout << "Path (reversed): ";
for (Path const* p = path; p != nullptr; p = p->prev)
std::cout << p << ".";
std::cout << std::endl;
}
}
};
Searcher{}(jsonPT);
Alternatives for writing the recursive traversal would be a C++14 generic lambda, or in C++11 a type-erased concrete lambda using std::function.
I'm using this program to display a list of all html tags in a given file:
#include <cstdio>
#include <libxml/HTMLparser.h>
#include <libxml/tree.h>
#include <iostream>
#include <cstring>
using namespace std;
static void
print_element_names(htmlNodePtr a_node)
{
htmlNodePtr cur_node = NULL;
for (cur_node = a_node; cur_node!=NULL; cur_node = cur_node->next) {
printf("node type: Element, name: %s\n", cur_node->name);
print_element_names(cur_node->children);
}
}
int main(int argc, char **argv) {
htmlDocPtr doc;
htmlNodePtr root_node;
doc = htmlReadFile(argv[1], NULL, 0);
root_node = xmlDocGetRootElement(doc);
print_element_names(root_node);
xmlFreeDoc(doc);
xmlCleanupParser();
return 0;
}
How do I get it to display the attributes as well (eg. href="something" for <a>)?
It seems there is no such field:
zajec#linux-lbnn:~/Prog_zesp> g++ `xml2-config --cflags --libs` -o tester tester.cpp
tester.cpp: In function ‘void print_element_names(xmlNode*)’:
tester.cpp:17: error: ‘struct _xmlNode’ has no member named ‘attributes’
=== EDIT ===
If I do something like this:
if (strcmp((char *)cur_node->name, "a")==0) {
cout << cur_node->properties->name << endl;
I get the name of the attribute - "href"
If I go one step further:
if (strcmp((char *)cur_node->name, "a")==0) {
cout << cur_node->properties->children->name << endl;
I get "text", but not the actual link.