C++ Iterate into Nested Struct Field with Boost Fusion Adapt_Struct

C++ iterate into nested struct field with boost fusion adapt_struct

I made an example of what you want that you can see at my blog site. In this is case it's a JSON serializer that works with nested structs. It uses a 'more Boost' solution since I saw it in the Boost.Serialization library. (See also below and live on Coliru.)

The solution uses Fusion Sequence adaptation of structs and a metafunction that walks object members (recursively) - using Boost.TypeTraits and different traits for specific types.

You can see a more complex example of the same solution at the site for googlecode corbasim project for creating an run-time reflexive API.

Code listing for the generic JSON serializer:

See it Live on Coliru

#ifndef JSON_SERIALIZER_HPP
#define JSON_SERIALIZER_HPP

#include <boost/type_traits.hpp> // is_array, is_class, remove_bounds

#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/identity.hpp>
#include <boost/mpl/next_prior.hpp>

#include <boost/fusion/mpl.hpp>
#include <boost/fusion/adapted.hpp> // BOOST_FUSION_ADAPT_STRUCT

// boost::fusion::result_of::value_at
#include <boost/fusion/sequence/intrinsic/value_at.hpp>
#include <boost/fusion/include/value_at.hpp>

// boost::fusion::result_of::size
#include <boost/fusion/sequence/intrinsic/size.hpp>
#include <boost/fusion/include/size.hpp>

// boost::fusion::at
#include <boost/fusion/sequence/intrinsic/at.hpp>
#include <boost/fusion/include/at.hpp>

namespace json
{

// Forward
template < typename T >
struct serializer;

namespace detail
{

namespace iterator
{

template < typename S, typename N >
struct Comma
{
template < typename Ostream >
static inline void comma(Ostream& os)
{
os << ", ";
}
};

template < typename S >
struct Comma< S, typename boost::mpl::prior< typename boost::fusion::result_of::size< S >::type >::type >
{
template < typename Ostream >
static inline void comma(Ostream& os)
{
}
};

// Iteracion sobre una estructura
template < typename S, typename N >
struct StructImpl
{
// Tipo del campo actual
typedef typename boost::fusion::result_of::value_at< S, N >::type current_t;
typedef typename boost::mpl::next< N >::type next_t;
typedef boost::fusion::extension::struct_member_name< S, N::value > name_t;

template < typename Ostream >
static inline void serialize(Ostream& os, const S& s)
{
os << "\"" << name_t::call() << "\": ";
::json::serializer< current_t >::serialize(os, boost::fusion::at< N >(s));

// Insert comma or not
Comma< S, N >::comma(os);

StructImpl< S, next_t >::serialize(os, s);
}
};

// Fin de la iteracion sobre estructuras.
template < typename S >
struct StructImpl< S, typename boost::fusion::result_of::size< S >::type >
{
template < typename Ostream >
static inline void serialize(Ostream& os, const S& s)
{
// Nada que hacer
}
};

// Iterador sobre una estructura. Template fachada.
template < typename S >
struct Struct : StructImpl< S, boost::mpl::int_< 0 > > {};

} // iterator

template < typename T >
struct array_serializer
{
typedef array_serializer< T > type;

typedef typename boost::remove_bounds< T >::type slice_t;

static const size_t size = sizeof(T) / sizeof(slice_t);

template < typename Ostream >
static inline void serialize(Ostream& os, const T& t)
{
os << "[";
for(size_t idx=0; idx<size; idx++)
{
::json::serializer< slice_t >::serialize(os, t[idx]);
if (idx != size-1)
os << ", ";
}
os << "]";
}

};

template < typename T >
struct struct_serializer
{
typedef struct_serializer< T > type;

template < typename Ostream >
static inline void serialize(Ostream& os, const T& t)
{
os << "{";
iterator::Struct< T >::serialize(os, t);
os << "}";
}
};

template < typename T >
struct arithmetic_serializer
{
typedef arithmetic_serializer< T > type;

template < typename Ostream >
static inline void serialize(Ostream& os, const T& t)
{
os << t;
}
};

template < typename T >
struct calculate_serializer
{
typedef
typename boost::mpl::eval_if< boost::is_array< T >,
boost::mpl::identity< array_serializer < T > >,
//else
typename boost::mpl::eval_if< boost::is_class< T >,
boost::mpl::identity< struct_serializer < T > >,
//else
boost::mpl::identity< arithmetic_serializer < T > >
>
>::type type;

};

} // detail

template < typename T >
struct serializer : public detail::calculate_serializer < T >::type
{
};

} // json

#endif // JSON_SERIALIZER_HPP

//#include "json.hpp"
#include <iostream>

struct my_other_struct
{
int my_other_integer;
};

struct my_struct
{
int my_integer;

typedef int my_array_t[2];
my_array_t my_array;

typedef my_other_struct my_other_structs_t[3];
my_other_structs_t my_other_structs;
};

BOOST_FUSION_ADAPT_STRUCT(my_struct, (int, my_integer) (my_struct::my_array_t, my_array) (my_struct::my_other_structs_t, my_other_structs))
BOOST_FUSION_ADAPT_STRUCT(my_other_struct, (int, my_other_integer))

int main(int argc, char *argv[])
{
my_struct s1 = my_struct { 1, { 42, -42 }, { { 11 }, { 22 }, { 33 } } };

json::serializer< my_struct >::serialize(std::cout, s1);

std::cout << std::endl;
}

Getting a list of member types from a boost fusion adapted struct

Helper for transforming adapted fusion struct to something like std::tuple:

template<class Adapted, template<class ...> class Tuple = std::tuple>
struct AdaptedToTupleImpl
{
using Size = boost::fusion::result_of::size<Adapted>;

template<size_t ...Indices>
static Tuple<typename boost::fusion::result_of::value_at_c<Adapted, Indices>::type...>
Helper(std::index_sequence<Indices...>);

using type = decltype(Helper(std::make_index_sequence<Size::value>()));
};

template<class Adapted, template<class ...> class Tuple = std::tuple>
using AdaptedToTuple = typename AdaptedToTupleImpl<Adapted, Tuple>::type;

Validation:

using AsTuple = AdaptedToTuple<A>;
static_assert(std::is_same_v<std::tuple<int, double, std::string>, AsTuple>);

Helper which applies metafunction to each type in tuple:

template<class List, template<class> class Func> struct ForEachImpl;

template<class ...Types, template<class ...> class List, template<class> class Func>
struct ForEachImpl<List<Types...>, Func>
{
using type = List<Func<Types>...>;
};

template<class List, template<class> class Func>
using ForEach = typename ForEachImpl<List, Func>::type;

Validation:

static_assert(std::is_same_v<ForEach<AsTuple, std::add_pointer_t>, std::tuple<int*, double*, std::string*>>);

Also take a look at Boost.MP11 library. It has mp_transform metafunction which is equivalent to ForEach function described above.

Boost Fusion: convert adapted struct type to text

I think you can get something similar to what you want by making some slight modifications on the code in this answer. You can easily get the member name using boost::fusion::extension::struct_member_name but, as far as I know, you can't directly get the member type name. You can get the member type using boost::fusion::result_of::value_at (amongst other options) and I've chosen to use Boost.TypeIndex to get its name (in varying degrees of prettiness, depending on the compiler and the types in question). All of this is assuming that you actually need the Fusion adaptation, if you don't you can probably get a simpler approach that does only what you need.

Full Code
Running on WandBox (gcc)

Running on rextester (vc)

#include <iostream>
#include <string>

#include <boost/mpl/range_c.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/fusion/include/zip.hpp>
#include <boost/fusion/include/at_c.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/mpl.hpp>

#include <boost/type_index.hpp>

namespace fusion=boost::fusion;
namespace mpl=boost::mpl;

struct Foo
{
int x;
int y;
double z;
};

BOOST_FUSION_ADAPT_STRUCT(Foo, x, y, z);

struct Bar
{
std::pair<int,int> p;
std::string s;
};

BOOST_FUSION_ADAPT_STRUCT(Bar, p, s);

template <typename Sequence>
struct Struct_member_printer
{
Struct_member_printer(const Sequence& seq):seq_(seq){}
const Sequence& seq_;
template <typename Index>
void operator() (Index) const
{

std::string member_type = boost::typeindex::type_id<typename fusion::result_of::value_at<Sequence,Index>::type >().pretty_name() ;
std::string member_name = fusion::extension::struct_member_name<Sequence,Index::value>::call();

std::cout << member_type << " " << member_name << "; ";
}
};
template<typename Sequence>
void print_struct(Sequence const& v)
{
typedef mpl::range_c<unsigned, 0, fusion::result_of::size<Sequence>::value > Indices;
std::cout << "{ ";
fusion::for_each(Indices(), Struct_member_printer<Sequence>(v));
std::cout << "}\n";
}

int main()
{
Foo foo;
print_struct(foo);

Bar bar;
print_struct(bar);
}

Iterate Over Struct; Easily Display Struct Fields And Values In a RichEdit Box

BOOST_FUSION_ADAPT_STRUCT seems to fit well here. For example:

// Your existing struct
struct Foo
{
int i;
bool j;
char k[100];
};

// Generate an adapter allowing to view "Foo" as a Boost.Fusion sequence
BOOST_FUSION_ADAPT_STRUCT(
Foo,
(int, i)
(bool, j)
(char, k[100])
)

// The action we will call on each member of Foo
struct AppendToTextBox
{
AppendToTextBox(RichEditControl& Ctrl) : m_Ctrl(Ctrl){}

template<typename T>
void operator()(T& t)const
{

m_Ctrl.Lines.Append(boost::lexical_cast<std::string>(t));
}

RichEditControl& m_Ctrl;

};

// Usage:
void FillTextBox(Foo& F, RichEditControl& Ctrl)
{
boost::fusion::for_each(F, AppendToTextBox(Ctrl));
}

Is it possible to generate a fusion map from an adapted struct?

You should use Associative Iterator interface - it provides result_of::key_of<I>::type metafunciton.

I have found code in my old records, and extracted relevant part.
I used it during implementation of SoA vector (as I understand from chat - you are implementing it too), but eventually switched to just explicit definition of fusion::map. I think I did that in order to have unified interface of normal structure and structure of references (i.e. both are accessed via type tag).

Live Demo on Coliru

namespace demo
{
struct employee
{
std::string name;
int age;
};
}

namespace keys
{
struct name;
struct age;
}

BOOST_FUSION_ADAPT_ASSOC_STRUCT
(
demo::employee,
(std::string, name, keys::name)
(int, age, keys::age)
)

template<typename> void type_is();

int main()
{
type_is<as_fusion_map<demo::employee>::type>();
}

Result is:

main.cpp:(.text.startup+0x5): undefined reference to `void type_is<

boost::fusion::map
<
boost::fusion::pair<keys::name, std::string>,
boost::fusion::pair<keys::age, int>,
boost::fusion::void_,
boost::fusion::void_,
boost::fusion::void_,
boost::fusion::void_,
boost::fusion::void_,
boost::fusion::void_,
boost::fusion::void_,
boost::fusion::void_
>

>()'

Here is full implementation

//             Copyright Evgeny Panasyuk 2012.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// Reduced from larger case, some includes may not be needed

#include <boost/fusion/algorithm/transformation/transform.hpp>
#include <boost/fusion/sequence/intrinsic/value_at_key.hpp>
#include <boost/fusion/include/adapt_assoc_struct.hpp>
#include <boost/fusion/sequence/intrinsic/at_key.hpp>
#include <boost/fusion/view/transform_view.hpp>
#include <boost/fusion/view/zip_view.hpp>
#include <boost/fusion/container/map.hpp>
#include <boost/fusion/algorithm.hpp>

#include <boost/mpl/transform.hpp>
#include <boost/mpl/vector.hpp>

#include <boost/static_assert.hpp>
#include <boost/type_traits.hpp>

#include <iostream>
#include <iterator>
#include <ostream>
#include <string>

#include <boost/mpl/push_front.hpp>
#include <boost/fusion/sequence.hpp>
#include <boost/fusion/iterator.hpp>
#include <boost/fusion/iterator/next.hpp>
#include <boost/fusion/iterator/equal_to.hpp>
#include <boost/fusion/iterator/key_of.hpp>
#include <boost/fusion/iterator/value_of.hpp>

using namespace boost;
using namespace std;

using fusion::at_key;
using fusion::at_c;

// ____________________________________________________________________________________ //

namespace res_of=boost::fusion::result_of;
using namespace boost::fusion;

template<typename Vector,typename First,typename Last,typename do_continue>
struct to_fusion_map_iter;

template<typename Vector,typename First,typename Last>
struct to_fusion_map_iter<Vector,First,Last,mpl::false_>
{
typedef typename res_of::next<First>::type Next;
typedef typename mpl::push_front
<
typename to_fusion_map_iter
<
Vector,
Next,
Last,
typename res_of::equal_to<Next,Last>::type
>::type,
fusion::pair
<
typename res_of::key_of<First>::type,
typename res_of::value_of_data<First>::type
>
>::type type;
};
template<typename Vector,typename First,typename Last>
struct to_fusion_map_iter<Vector,First,Last,mpl::true_>
{
typedef Vector type;
};

template<typename FusionAssociativeSequence>
struct as_fusion_map
{
typedef typename res_of::begin<FusionAssociativeSequence>::type First;
typedef typename res_of::end<FusionAssociativeSequence>::type Last;
typedef typename res_of::as_map
<
typename to_fusion_map_iter
<
mpl::vector<>,
First,
Last,
typename res_of::equal_to<First,Last>::type
>::type
>::type type;
};

// ____________________________________________________________________________________ //

// Defenition of structure:

namespace demo
{
struct employee
{
std::string name;
int age;
};
}

namespace keys
{
struct name;
struct age;
}

BOOST_FUSION_ADAPT_ASSOC_STRUCT
(
demo::employee,
(std::string, name, keys::name)
(int, age, keys::age)
)

// ____________________________________________________________________________________ //
template<typename> void type_is();

int main()
{
type_is<as_fusion_map<demo::employee>::type>();
}

boost::spirit recursive imperative c++ grammar: BOOST_FUSION_ADAPT_STRUCT fails

There's a number of issues.

  1. Like the comment said, don't use using-directives; they land you in trouble
  2. if all you want to concatenate all source strings in nestedSomething then, just wrap it all in a raw[] (or as_string[raw[...]] but that's not even necessary), e.g.

    nestedSomething = !recursiveImpCpp >> qi::raw[*~char_("(){}")
    >> -('(' >> nestedSomething >> ')')
    >> -('{' >> nestedSomething >> '}')
    >> !recursiveImpCpp >> *~char_("(){}")];
  3. that rule is broken in the sense that it will match an empty string. This makes the grammar never end (it will match an "infinite" amount of empty nestedSomething). You will have to decide on some non-optional part. Here's a brute-force fix:

    qi::raw[...] [ qi::_pass = px::size(qi::_1) > 0 ];

    Even with that the grammar gets stuck in an infinite loop for non-trivial programs (try it with its own source). The following should clear things up a little:

    identifier_soup = !recursiveImpCpp >> +~qi::char_("(){}");
    parenthesized = '(' >> -nestedSomething >> ')';
    bracketed = '{' >> -nestedSomething >> '}';
    nestedSomething %= qi::raw[ -identifier_soup
    >> -parenthesized
    >> -bracketed
    >> -identifier_soup] [ qi::_pass = px::size(qi::_1) > 0 ];

    But that will still not parse e.g. int main() { if(true) { std::cout << "Yes\n"; } else { std::cout << "No\n"; } }). The reason is that main(<parenthesized>){<bracketed>} does only accept nestedSomething inside the brackets; that expressly prohibits the if-else construct...

    Let's rename ifElseContent to something proper (like statement)

    block     = '{' >> startRule >> '}';
    statement = block | singleStatement;

    and use it instead of the bracketed:

    nestedSomething %= qi::raw[ -identifier_soup
    >> -parenthesized
    >> -block
    >> -identifier_soup] [ qi::_pass = boost::phoenix::size(qi::_1) > 0 ];

General various notes

  • you can simplify the includes, instead of wrapping them in regions to hide them
  • you can simplify the rest too (see my demo)
  • the grammar is going to be quite inefficient with all the negative look-aheads. Consider tokenizing or using a much more finegrained keyword detection scheme (using expectation points and or distinct()[] from the Qi Repository)

Partial Demo

Applying the notes above:

Live On Coliru

#define BOOST_SPIRIT_DEBUG
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/fusion/include/adapted.hpp>
#include <boost/optional/optional_io.hpp>

#ifdef BOOST_SPIRIT_DEBUG
namespace std {
template <typename... T, typename... V>
auto& operator<<(basic_ostream<T...>& os, vector<V...> const& v) {
os << "{";
for (auto& el : v) os << el << " ";
return os;
}
}
#endif

namespace qi = boost::spirit::qi;

/* later make this variant with all impStatementVariants -> either make this a
* vector to have sequences on all levels or make imperativeCpp derive from
* this

-> typedef variant<
recursive_wrapper<ifElseStruct>,
recursive_wrapper<switchStruct>,
recursive_wrapper<forStruct>,
recursive_wrapper<whileStruct>,
recursive_wrapper<doWhileStruct>

*
*/
struct ifElseStruct;
typedef boost::variant<ifElseStruct, std::string> someSeqNode;

struct ifElseStruct
{
std::string ifCond;
std::vector<someSeqNode> ifContent;
boost::optional<std::vector<someSeqNode>> elseContent;

friend std::ostream& operator<< (std::ostream& stream, const ifElseStruct& var) {
stream << "ifCond: " << var.ifCond << " ifContent: " << var.ifContent << std::endl << "elseContent:" << var.elseContent;
return stream;
}
};

BOOST_FUSION_ADAPT_STRUCT(ifElseStruct, ifCond, ifContent, elseContent)

//GRAMMAR for flowcontrol (branching and looping)
template<typename Iterator, typename Skipper> struct imperativeGrammar :qi::grammar<Iterator, std::vector<someSeqNode>(), Skipper>
{
imperativeGrammar() : imperativeGrammar::base_type(startRule)
{
startRule = *(recursiveImpCpp | nestedSomething); //vector<variant<ifElseStruct(), string>>
recursiveImpCpp = ifElseNormalRule.alias() /*| switchRule | whileRule | forRule ...*/;

//attr: ifElseStruct containing-> string, vector<someSeqNode>, optional<vector<someSeqNode>>
ifElseNormalRule = qi::lit("if") >> '(' >> condition >> ')' >> statement >> -("else" >> statement);

condition = *~qi::char_(")");//TODO: replace with nestedSomething rule
block = '{' >> startRule >> '}';
statement = block | singleStatement;

identifier_soup = !recursiveImpCpp >> +~qi::char_("(){}");
parenthesized = '(' >> -nestedSomething >> ')';
bracketed = '{' >> -nestedSomething >> '}';
nestedSomething %= qi::raw[ -identifier_soup
>> -parenthesized
>> -block
>> -identifier_soup] [ qi::_pass = boost::phoenix::size(qi::_1) > 0 ];
singleStatement = !recursiveImpCpp >> qi::raw[*~qi::char_(';') >> ';'];

BOOST_SPIRIT_DEBUG_NODES((startRule)(ifElseNormalRule)(statement)(block)(nestedSomething)(identifier_soup)(parenthesized)(bracketed)(singleStatement))
}

qi::rule<Iterator, std::vector<someSeqNode>(), Skipper> startRule;
qi::rule<Iterator, ifElseStruct(), Skipper> recursiveImpCpp;
qi::rule<Iterator, ifElseStruct(), Skipper> ifElseNormalRule;

qi::rule<Iterator, std::string(), Skipper> condition;
qi::rule<Iterator, std::vector<someSeqNode>(), Skipper> block, statement;
qi::rule<Iterator, std::string(), Skipper> nestedSomething;
qi::rule<Iterator, std::string(), Skipper> singleStatement;
qi::rule<Iterator, Skipper> identifier_soup, parenthesized, bracketed;

/*qi::rule<Iterator, Skipper> forRule;
qi::rule<Iterator, Skipper> switchCaseBreakRule;
qi::rule<Iterator, Skipper> whileRule;
qi::rule<Iterator, Skipper> doWhileRule;*/
};

#include <fstream>

int main() {
//std::string const input = { std::istreambuf_iterator<char>(std::ifstream("main.cpp").rdbuf()), {} };
std::string const input = "int main() { if(true) { std::cout << \"Yes\\n\"; } else { std::cout << \"No\\n\"; } }";

using It = std::string::const_iterator;
It f(input.begin()), l(input.end());

imperativeGrammar<It, qi::space_type> p;
std::vector<someSeqNode> rep;
bool ok = phrase_parse(f, l, p, qi::space, rep);
if (ok) {
std::cout << "Parse success: " << rep << "\n";
}
else
std::cout << "Parse failure\n";

if (f!=l)
std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
}

Prints:

Parse success: {int main() { if(true) { std::cout << "Yes\n"; } else { std::cout << "No\n"; } } 

With debug info

<startRule>
<try>int main() { if(true</try>
<ifElseNormalRule>
<try>int main() { if(true</try>
<fail/>
</ifElseNormalRule>
<nestedSomething>
<try>int main() { if(true</try>
<identifier_soup>
<try>int main() { if(true</try>
<ifElseNormalRule>
<try>int main() { if(true</try>
<fail/>
</ifElseNormalRule>
<success>() { if(true) { std:</success>
<attributes>[]</attributes>
</identifier_soup>
<parenthesized>
<try>() { if(true) { std:</try>
<nestedSomething>
<try>) { if(true) { std::</try>
<identifier_soup>
<try>) { if(true) { std::</try>
<ifElseNormalRule>
<try>) { if(true) { std::</try>
<fail/>
</ifElseNormalRule>
<fail/>
</identifier_soup>
<parenthesized>
<try>) { if(true) { std::</try>
<fail/>
</parenthesized>
<block>
<try>) { if(true) { std::</try>
<fail/>
</block>
<identifier_soup>
<try>) { if(true) { std::</try>
<ifElseNormalRule>
<try>) { if(true) { std::</try>
<fail/>
</ifElseNormalRule>
<fail/>
</identifier_soup>
<fail/>
</nestedSomething>
<success> { if(true) { std::c</success>
<attributes>[]</attributes>
</parenthesized>
<block>
<try> { if(true) { std::c</try>
<startRule>
<try> if(true) { std::cou</try>
<ifElseNormalRule>
<try> if(true) { std::cou</try>
<statement>
<try> { std::cout << "Yes</try>
<block>
<try> { std::cout << "Yes</try>
<startRule>
<try> std::cout << "Yes\n</try>
<ifElseNormalRule>
<try> std::cout << "Yes\n</try>
<fail/>
</ifElseNormalRule>
<nestedSomething>
<try> std::cout << "Yes\n</try>
<identifier_soup>
<try>std::cout << "Yes\n"</try>
<ifElseNormalRule>
<try>std::cout << "Yes\n"</try>
<fail/>
</ifElseNormalRule>
<success>} else { std::cout <</success>
<attributes>[]</attributes>
</identifier_soup>
<parenthesized>
<try>} else { std::cout <</try>
<fail/>
</parenthesized>
<block>
<try>} else { std::cout <</try>
<fail/>
</block>
<identifier_soup>
<try>} else { std::cout <</try>
<ifElseNormalRule>
<try>} else { std::cout <</try>
<fail/>
</ifElseNormalRule>
<fail/>
</identifier_soup>
<success>} else { std::cout <</success>
<attributes>[[s, t, d, :, :, c, o, u, t, , <, <, , ", Y, e, s, \, n, ", ;, ]]</attributes>
</nestedSomething>
<ifElseNormalRule>
<try>} else { std::cout <</try>
<fail/>
</ifElseNormalRule>
<nestedSomething>
<try>} else { std::cout <</try>
<identifier_soup>
<try>} else { std::cout <</try>
<ifElseNormalRule>
<try>} else { std::cout <</try>
<fail/>
</ifElseNormalRule>
<fail/>
</identifier_soup>
<parenthesized>
<try>} else { std::cout <</try>
<fail/>
</parenthesized>
<block>
<try>} else { std::cout <</try>
<fail/>
</block>
<identifier_soup>
<try>} else { std::cout <</try>
<ifElseNormalRule>
<try>} else { std::cout <</try>
<fail/>
</ifElseNormalRule>
<fail/>
</identifier_soup>
<fail/>
</nestedSomething>
<success>}


Related Topics



Leave a reply



Submit