Generating types using a macro from XML schema

Hi all, I’ve been scratching my head on this all morning, and I can’t figure what’s missing… I’m trying to define types based on the contents of an xml file and its schema. This is not unlike Trouble understanding macros: creating a struct on the fly - #6 by JonathanAnderson but I did not manage to make it work anyway.

I figured that, in order to create one type per XML element, I could loop over the contents of the XML file to generate all the types using a macro.

As an example, I created a small XML string and parsed it with EzXML. I’m trying to use the “Name” property as a type name and the rest of the attributes as properties.

fruitsxml = parsexml("""
<Fruits>
  <Fruit Name = "Apple" Description = "Apples are indeed fruits"/>
  <Fruit Name = "Tomato" Description = "Yes tomatoes are fruits !"/>
</Fruits>
""")

Now to the macro. I need to retrieve the value of the “Name” property of my XML element so I need to escape it first then interpolate it, then get the property. Same thing for the description.
However when I try to use the associated expression in a subsequent quote block, I get a ERROR: syntax: invalid type signature. So I assume some expression is not properly expanded or else there.

macro generate_fruit(fruit)
    fruit_name = :($(esc(fruit))["Name"])
    fruit_description = :($(esc(fruit))["Description"])
    ex = quote
        struct $fruit_name
            description::String
            function $fruit_name()
                return new($fruit_description)
            end
        end
    end
    return esc(ex)
end

firstfruit = firstelement(root(fruitsxml))

@macroexpand @generate_fruit(firstfruit)

The macro expansion gives

quote
    struct ($(Expr(:escape, :firstfruit)))["Name"]
        description::String
        function (($(Expr(:escape, :firstfruit)))["Name"])()
            return new(($(Expr(:escape, :firstfruit)))["Description"])
        end
    end
end

while I would like the actual values instead of the expressions, is there some eval magic to add there ?

While testing other syntaxes I tried

macro generate_fruit_quote(fruit)
    quote
        fruit_name = $(esc(fruit))["Name"]
        fruit_description = $(esc(fruit))["Description"]
        ex = quote
            struct $fruit_name
                description::String
                function $fruit_name()
                    return new($fruit_description)
                end
            end
        end
        return esc(ex)
    end
end

For which @macroexpand gave

:($(Expr(:escape, quote
    struct "Apple"
        description::String
        function ("Apple")()
            return new("Apples are indeed fruits")
        end
    end
end)))

Which shows that the underlying variables are almost properly expanded inside the struct definition, but not the expression itself which is quoted.
Could someone explain the proper way to do this ?

EDIT n°1 i was missing a Symbol in fruit_name to get a proper type name

EDIT n°2 I think I’ve got it now. If I understand correctly, I just missed an eval in my initial implementation instead of the esc… I someone could clarify that would be perfect though.

To make it cleaner, I put everything inside a module which leads to

module MyFruits

using EzXML

fruitsxml = parsexml("""
<Fruits>
  <Fruit Name = "Apple" Description = "Apples are indeed fruits"/>
  <Fruit Name = "Tomato" Description = "Yes tomatoes are fruits !"/>
</Fruits>
""")

macro generate_fruit(fruit)
    fruit_name_type = eval(:(Symbol($(fruit)["Name"])))
    fruit_description = :($(fruit)["Description"])
    ex = quote
        struct $fruit_name_type
            description::String
            function $fruit_name_type()
                return new($fruit_description)
            end
        end
    end
    return ex
end

for efruit in eachelement(root(fruitsxml))
    @eval @generate_fruit $efruit
end

end

Upon evaluation this gives me a Main.MyFruits module with an Apple and a Tomato types