How can I simply parse a CSS like (!) file in my Qt application?

核能气质少年 提交于 2019-11-29 06:49:24

I know two possibilities:

  1. boost::spirit and here you can find a good introduction to the boost::spirit parser framework
  2. I would recommend to write your own recursive descent parser

Due to the fact, that your personalized *.pss is not that complex as a CSS (simple bracketing etc.), I would recommend 2.

There's nothing public within Qt. You're of course free to use the Qt's private CSS parser - you can copy it and modify to fit your needs.

See qtbase/src/gui/text/qcssparser_p.h, in qtbase/src/gui/text.

The good news is that for the example you've shown above, the modifications would be very minor. Qt's CSS parser already supports @import, so we only additional bit of syntax you have is the nested selector syntax. Without that syntax, you can use QCss::Parser as-is. The parser was written in a flexible fashion, where you don't need to worry about formal CSS keywords: it will still let you access all the declarations, whether they make sense from the formal CSS point of view or not.

Iterating the parse tree is as simple as it gets:

int main() {
   QCss::Parser parser(pss);
   QCss::StyleSheet styleSheet;
   if (!parser.parse(&styleSheet))
      return 1;
   for (auto rule : styleSheet.styleRules) {
      qDebug() << "** Rule **";
      for (auto sel : rule.selectors) {
        for (auto bSel : sel.basicSelectors)
           qDebug() << bSel;
      }
      for (auto decl : rule.declarations)
         qDebug() << decl;
   }
}

The output is what we'd expect:

** Rule **
BasicSelector "propertyID"="1230000"
Declaration "fillColor" = '#f3f1ed' % QColor(ARGB 1, 0.952941, 0.945098, 0.929412)
Declaration "minSize" = '5' % 5
Declaration "lineWidth" = '3'
** Rule **
BasicSelector "propertyID"="124???|123000"
Declaration "lineType" = 'dotted'
** Rule **
BasicSelector "propertyID"="125???"
Declaration "lineType" = 'thinline'
** Rule **
BasicSelector "propertyID"="133???"
Declaration "lineType" = 'thickline'

We have to implement the debug stream operators for QCss classes ourselves:

QDebug operator<<(QDebug dbg, const QCss::AttributeSelector & sel) {
   QDebugStateSaver saver(dbg);
   dbg.noquote().nospace() << "\"" << sel.name << "\"";
   switch (sel.valueMatchCriterium) {
   case QCss::AttributeSelector::MatchEqual:
      dbg << "="; break;
   case QCss::AttributeSelector::MatchContains:
      dbg << "~="; break;
   case QCss::AttributeSelector::MatchBeginsWith:
      dbg << "^="; break;
   case QCss::AttributeSelector::NoMatch:
      break;
   }
   if (sel.valueMatchCriterium != QCss::AttributeSelector::NoMatch && !sel.value.isEmpty())
      dbg << "\"" << sel.value << "\"";
   return dbg;
}

QDebug operator<<(QDebug dbg, const QCss::BasicSelector & sel) {
   QDebugStateSaver saver(dbg);
   dbg.noquote().nospace() << "BasicSelector";
   if (!sel.elementName.isEmpty())
      dbg << " #" << sel.elementName;
   for (auto & id : sel.ids)
      dbg << " id:" << id;
   for (auto & aSel : sel.attributeSelectors)
      dbg << " " << aSel;
   return dbg;
}

When traversing the declaration, the QCss::parser already interprets some standard values for us, e.g. colors, integers, etc.

QDebug operator<<(QDebug dbg, const QCss::Declaration & decl) {
   QDebugStateSaver saver(dbg);
   dbg.noquote().nospace() << "Declaration";
   dbg << " \"" << decl.d->property << "\" = ";
   bool first = true;
   for (auto value : decl.d->values) {
      if (!first) dbg << ", ";
      dbg << "\'" << value.toString() << "\'";
      first = false;
   }
   if (decl.d->property == "fillColor")
      dbg << " % " << decl.colorValue();
   else if (decl.d->property == "minSize") {
      int i;
      if (decl.intValue(&i)) dbg << " % " << i;
   }
   return dbg;
}

Finally, the boilerplate and the stylesheet to be parsed:

// https://github.com/KubaO/stackoverflown/tree/master/questions/css-like-parser-31583622
#include <QtGui>
#include <private/qcssparser_p.h>

const char pss[] =
  "/* @include \"otherStyleSheet.pss\"; */ \
  [propertyID=\"1230000\"] {  \
    fillColor : #f3f1ed; \
    minSize : 5; \
    lineWidth : 3; \
  } \
   \
  /* sphere */ \
  [propertyID=\"124???|123000\"] {  \
    lineType : dotted; \
  } \
   \
  /* square */ \
  [propertyID=\"125???\"] { \
    lineType : thinline; \
  } \
   \
  /* ring */ \
  [propertyID=\"133???\"] { \
    lineType : thickline;  \
    /*[hasInnerRing=true] { \
      innerLineType : thinline; \
    }*/   \
  }";

Support for nested selectors/rules can be implemented by modifying the parser source. The change needed to make Parser::parseRuleset recursive is very minor. I'll leave this as the exercise for the reader :)

All in all, I'd think that reusing the existing parser is much easier than rolling your own, especially as your users will inevitably wish you to support more and more of the CSS spec.

Well, I'm guessing you don't want to be in the business of writing an Object parser, you would just be reinventing JSON, or YAML, or the like. So your best bet is to make your formatting conform to a known configuration or object notation language and then parse it with some library for the language you are using. With very minor modification, the format you describe above could become HOCON, which is a very nice superset of JSON, and has syntax much closer to what you are using:

https://github.com/typesafehub/config/blob/master/HOCON.md

You could then parse it with a HOCON parsing library, and voila, you would have in-memory objects you can model or store any way you please. I believe Qt is C++ based? There is a hocon library for C, I don't know about C++, and I'm guessing you would need to write a Qt plug-in to wrap the HOCON parsing from some other language.

The other option is to use a CSS->object parser like this one: https://github.com/reworkcss/css

Which you may need to fork and modify to your needs. Either way, I'm guessing that to integrate into a Qt app you will need a plug-in that handles some call-out to a command-line process or other code module.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!