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 .

Ralf Wickum
  • 2,850
  • 9
  • 55
  • 103
  • I'm not aware of anything being available. Would that be out-in-the-wild CSS or CSS you have control over? – Frank Osterfeld Jul 23 '15 at 09:47
  • @FrankOsterfeld Sorry, I do not know the difference out-in-the-wild and having a control over? I edited my question.. Thnx – Ralf Wickum Jul 23 '15 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. – Kuba hasn't forgotten Monica Jul 29 '15 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. – Ralf Wickum Aug 18 '15 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. – Kuba hasn't forgotten Monica Aug 20 '15 at 12:32
  • @Kuba Ober Qt's Parser: I only know http://doc.qt.io/qt-5/qdomdocument.html wich is specialized for XML style formatted documents. Where can I find that Qt parser? – Ralf Wickum Aug 20 '15 at 13:40
  • Um, my answer has a link. It *is* a private implementation as I've said, but hey, that's why you use Qt: you're free to repurpose its code as you see fit. The source is all there, just for you :) Just because it's private doesn't mean you are not supposed to use it - you can use it, you just have to know what you're doing. – Kuba hasn't forgotten Monica Aug 20 '15 at 14:33
  • Is there any formal definition of the PSS syntax? And are the people who write these files aware that they are *not* CSS, and that when writing them they should look up the document describing PSS, and not random CSS tutorials online? Basically, saying "it's CSS-like, but not CSS" is truly not enough to do *anything* with these files. You and the creators/maintainers of these files must be talking about the same thing, with at least a handwritten page or two with BNF description of the syntax. – Kuba hasn't forgotten Monica Aug 27 '15 at 12:59

3 Answers3

10

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.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • https://github.com/qtproject/qtbase/blob/dev/src/gui/text/qcssparser.cpp has over 2800 lines of code. – Ralf Wickum Aug 27 '15 at 07:18
  • @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 :) – Kuba hasn't forgotten Monica Aug 27 '15 at 12:35
  • I had a look at all required files like: https://github.com/qtproject/qtbase/blob/dev/src/gui/text/qcssparser_p.h , https://github.com/qtproject/qtbase/blob/dev/src/gui/text/qcssparser.cpp and https://github.com/qtproject/qtbase/blob/dev/src/gui/text/qcssscanner.cpp. 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. – Ralf Wickum Aug 27 '15 at 12:49
  • @RalfWickum Understood. Make sure you use good test cases, and fuzz it well if you expect to use input from the web. – Kuba hasn't forgotten Monica Aug 27 '15 at 12:55
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.

stolli
  • 5,580
  • 2
  • 26
  • 37
  • 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. – Ralf Wickum Aug 27 '15 at 07:20
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.

Murat
  • 716
  • 1
  • 6
  • 14
  • The simple recursve descent parser examples I found, look quite promising. boost::spirit seem to be developed on that. Thnx. – Ralf Wickum Aug 27 '15 at 07:11