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

我怕爱的太早我们不能终老 提交于 2019-12-29 06:51:08

问题


I have a document in a *.css (Cascading Style Sheets) like format, but it has its own keywords. Actually it is a personalized css (I call it *.pss), with own tags and properties. here I have an excerpt:

/* CSS like style sheet file *.pss */

@include "otherStyleSheet.pss";

/* comment */
[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;
  }  
}

I would like to parse it very easily, is there already something Ready-To-Use from Qt? What would be the easiest way?

Since *.css has its own keywords, I am NOT interessted in CSS parsers.

My further intention after parsing that *.pss is to store its properties in a Model structure .


回答1:


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.




回答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.




回答3:


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.



来源:https://stackoverflow.com/questions/31583622/how-can-i-simply-parse-a-css-like-file-in-my-qt-application

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