Search code examples
boostboost-spirit-x3

Parsing multiple CSS selectors with Boost Spirit X3


I am trying to parse multiple CSS selectors with Boost Spirit X3. If I have the following CSS rule that apply to multiple IDs (selectors) for example:

#ID1, #ID2, #ID3 { color:#000000; }

Where #ID1, #ID2, #ID3 are the selectors, color:#000000; is the declaration-block, and the same declaration-block applies to all 3 selectors. And assuming that the structs for rule, selector, and declaration_block are the following:

struct Rule {
   Selector selector;
   DeclationBlock declarationBlock;
}

struct Selector {
std::string target;
}

struct DeclarationBlock {
std::vector<Declaration> declarations;
}

And I already have Spirit rules for selectors and declaration-blocks:

auto selector = x3::rule<struct SelectorId, css::Selector>{"selector"};
auto declaration_block = x3::rule<struct DeclarationBlockId, css::DeclarationBlock>{"declaration-block"};

Parsing rules for single selectors would be straight forward:

auto rule = x3::rule<struct RuleId, css::Rule>{"rule"} = selector >> declaration_block;
auto rules = x3::rule<struct RulesId, std::vector<css::Rule>>{"rules"} = *rule;

But, my question is, how do I parse the same declaration block for multiple selectors? I am trying to use semantic actions to basically copy the declaration-block for all selectors, but I don't know if this would be the best approach.


Solution

  • I'm not sure whether it's possible to use the attribute of the declaration block's parser for multiple selectors. Most probably not.

    I would rather change the AST (and parsers) in such a way that it reflects the actual code:

    struct Rule {
       std::vector<Selector> selectors;  // why not?
       DeclationBlock declarationBlock;
    }
    
    auto const selectors = selector % ",";
    

    If you prefer to have a 1:1 correspondence between selector and declarationBlock then, having the Rule defined as above, it should be possible to use it in another parser like this:

    struct SingleRule
    {
        Selector selector;
        DeclationBlock declarationBlock;
    };
    
    auto unwrap = [](auto & ctx)
    {
        // iterate through all the selectors
        // and add them to the vector together with linked
        // declaration block
        for (auto const & s: _attr(ctx).selectors)
            _val(ctx).push_back(SingleRule{s, _attr(ctx).declarationBlock});
    };
    
    auto const unwrapped_rules
        = x3::rule<struct UnwrappedRulesId, std::vector<css::SingleRule>>
        = rule [unwrap];
    

    Not sure it's a good idea though. Maybe, it's easier to throw as closest and as simplest AST out from the parser, and then to make all the simplifications and optimizations later on.

    Also, there is already a CSS-parser written using Boost.Spirit.X3. You can try to use it as a reference or inspiration.