如何在我的Qt应用程序中简单解析类似CSS的(!)文件?

9

我有一个以*.css(层叠样式表)格式呈现的文档,但它具有自己的关键词。实际上,这是一种个性化的css文件(我称之为*.pss),具有自己的标签和属性。以下是其中的一部分:

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

我希望能够轻松解析它,Qt 中是否已经有现成的东西?最简单的方法是什么?
由于 *.css 有自己的关键字,所以我对 CSS 解析器不感兴趣。
在解析 *.pss 后,我的进一步意图是将其属性存储在 Model 结构中。

我不知道是否有任何可用的东西。那是野外CSS还是您可以控制的CSS? - Frank Osterfeld
PSS(不是CSS,仅类似CSS)是由我大学的一个部门提供的。它描述属性对象的样式属性。 - Ralf Wickum
“一个简单的解析结构化文件的示例或说明是我期望的。”事实上,没有“简单”的示例。你要么编写自己的解析器,为此你必须阅读并理解Sahara干燥的CSS标准,要么重用Qt的解析器。无论哪种方式都是实质性的。当然,可能有其他CSS解析器比Qt更容易修改。 - Kuba hasn't forgotten Monica
@Kuba Ober Qt的解析器:我只知道http://doc.qt.io/qt-5/qdomdocument.html,它专门用于XML格式的文档。我在哪里可以找到那个Qt解析器? - Ralf Wickum
嗯,我的回答里有一个链接。正如我所说的那样,它是一个私有实现,但是嘿,这就是为什么你使用Qt:你可以自由地重新利用它的代码。源代码都在那里,只为你而设 :) 仅仅因为它是私有的并不意味着你不能使用它 - 你可以使用它,只要你知道你在做什么。 - Kuba hasn't forgotten Monica
显示剩余3条评论
3个回答

10

Qt中没有公开的内容。当然,您可以自由使用Qt的私有CSS解析器——您可以复制它并修改以适应您的需求。

请参见qtbase/src/gui/text/qcssparser_p.h,位于qtbase/src/gui/text中。

好消息是,对于您上面展示的示例,修改将非常小。Qt的CSS解析器已经支持@import,因此您需要的只是嵌套选择器语法(nested selector syntax)。如果没有该语法,则可以直接使用QCss::Parser。该解析器以灵活的方式编写,您不需要担心正式CSS关键字:它仍将让您访问所有声明,无论它们是否符合正式CSS的要求。

遍历解析树就像简单得多:

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;
   }
}

输出结果与我们预期的相同:

** 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'

我们需要自己实现QCss类的调试流操作符:
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;
}

当遍历声明时,QCss::parser已经为我们解释了一些标准值,例如颜色、整数等。
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;
}

最后,需要解析的是样板和样式表:
// 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; \
    }*/   \
  }";

支持嵌套选择器/规则可以通过修改解析器源代码来实现。使Parser::parseRuleset递归的所需更改非常小。我会把这个作为读者的练习 :)
总的来说,我认为重用现有的解析器比自己编写更容易,特别是当你的用户不可避免地希望你支持越来越多的CSS规范时。

https://github.com/qtproject/qtbase/blob/dev/src/gui/text/qcssparser.cpp 有超过2800行的代码。 - Ralf Wickum
@RalfWickum 那有什么问题呢?我的意思是 - 这是一个完整的CSS词法分析器/解析器,具有具体的语法树。你不应该修改所有2800行代码,只需进行所需的更改并将其全部使用即可。通过自己实现它,您最终会得到大约相同数量的代码,但未经过广泛测试。从Qt中获得的优势是,其整个代码库都可以随意使用。这很好,而不是坏事 :) - Kuba hasn't forgotten Monica
我查看了所有必需的文件,如: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和https://github.com/qtproject/qtbase/blob/dev/src/gui/text/qcssscanner.cpp。我更喜欢自己写4000行代码,而不是导入不理解其中发生了什么。我并不是说这个解析器不好。只是对我来说太复杂了。 - Ralf Wickum
@RalfWickum 已经明白。请确保使用好的测试用例,并且如果您希望使用来自网络的输入,请进行充分模糊测试。 - Kuba hasn't forgotten Monica

1

我猜想你不想从事编写对象解析器的业务,因为那样你只会重新发明JSON、YAML或类似的东西。所以你最好让你的格式符合某种已知的配置或对象表示语言,然后使用所使用语言的一些库来解析它。通过非常小的修改,你描述的格式可以成为HOCON,它是JSON的一个非常好的超集,并且其语法更接近于你所使用的:

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

你可以使用HOCON解析库对其进行解析,然后就有了内存对象,可以按照自己的方式建模或储存。我认为Qt是基于C++的?有一个适用于C的hocon库,不确定是否适用于C++,猜测你需要编写一个Qt插件来包装来自其他语言的HOCON解析器。
另一种选择是使用像这样的CSS->对象解析器:https://github.com/reworkcss/css,也许需要分叉并修改以满足你的需求。无论哪种方式,我猜想要集成到Qt应用程序中,你需要一个处理某些调用命令行进程或其他代码模块的插件。

我也会以不同的文件格式来处理它。但是由于我们从另一个部门得到了这个格式,所以我不能(也不可能)去改变它。 - Ralf Wickum

1
我知道两种可能性:
  1. boost::spirithere,你可以找到一个很好的介绍boost::spirit解析器框架的地方。
  2. 我建议你编写自己的递归下降解析器
由于你个性化的*.pss并不像CSS那样复杂(简单的括号等),我建议选择第二种方法。

我发现的简单递归下降解析器示例看起来非常有前途。boost::spirit似乎是在此基础上开发的。谢谢。 - Ralf Wickum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接