9

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 .

8
  • I'm not aware of anything being available. Would that be out-in-the-wild CSS or CSS you have control over? Commented Jul 23, 2015 at 9:47
  • @FrankOsterfeld Sorry, I do not know the difference out-in-the-wild and having a control over? I edited my question.. Thnx Commented Jul 23, 2015 at 10:08
  • What Frank means is: is the CSS a part of your application, so that you can edit it and manage it, or does it come from potentially malicious sources on the internet? Remember that if your CSS parser has bugs, malicious CSS can exploit them and take over your application. In less-than-modern C++ (or C) code, such bugs are more the norm than exception. Qt's own parser is not designed to be resilient against malicious CSS - it was never designed to accept random input from internet. It has plenty of security holes, I'm sure. Commented Jul 29, 2015 at 13:06
  • The PSS (it is not a CSS, it is just CSS like) is provided from a department in my university. And it decribes style attributes of property objects. Commented Aug 18, 2015 at 11:23
  • "An easy example or description how to parse such a structured file would be my expectation." There are no "easy" examples. Either you write your own parser, and for that you have to read and understand the Sahara dry CSS standard, or you re-use Qt's parser. Either way is substantial. Of course there may be other CSS parsers out there that are easy or easier to modify than Qt's. Commented Aug 20, 2015 at 12:32

3 Answers 3

11

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.

Sign up to request clarification or add additional context in comments.

4 Comments

@RalfWickum And what's wrong with that? I mean - it's a complete CSS lexer/parser with a concrete syntax tree. You aren't supposed to modify all 2800 lines, make the change(s) you need and use it whole. By implementing it yourself, you'll end up with roughly the same amount of code that has not been as extensively tested. The leverage you get from Qt is that its whole codebase is yours for use as you please. That there's a lot of it is a good thing, not a bad thing :)
I had a look at all required files like: github.com/qtproject/qtbase/blob/dev/src/gui/text/… , github.com/qtproject/qtbase/blob/dev/src/gui/text/… and github.com/qtproject/qtbase/blob/dev/src/gui/text/…. I am much more comfortable to write a total of 4000 lines of code instead of importing it without understand whats going on there. I dont say, that parser is bad. It was just too complex for me.
@RalfWickum Understood. Make sure you use good test cases, and fuzz it well if you expect to use input from the web.
1
+50

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.

1 Comment

The simple recursve descent parser examples I found, look quite promising. boost::spirit seem to be developed on that. Thnx.
1

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.

1 Comment

I'd do that file format differently as well. But since we got that format from another department, I am not going (and I am not able) to change it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.