Russian Qt Forum

Qt => Общие вопросы => Тема начата: __Heaven__ от Март 09, 2016, 07:43



Название: Парсинг строк
Отправлено: __Heaven__ от Март 09, 2016, 07:43
Привет, друзья!
Есть файл, который содержит строки типа

Цитировать
*node(363484,232.7625,-113.996,40.69956,0,0,0,0,0)
здесь первая цифра - номер узла, три следующие - его координаты.

Написал код, который считывает строку, но выглядит он коряво на мой взгляд.

Код
C++ (Qt)
   while (!fileStream_.atEnd()){
       QString line(fileStream_.readLine());
       if (line == "END NODES"){
           break;
       }
 
       Q_ASSERT(line.startsWith("*node("));
       QTextStream lineStream(&line, QIODevice::ReadOnly);
 
       lineStream.seek(6);
 
       ParsedNode node;
       lineStream >> node.number;
 
       float x;
       float y;
       float z;
 
       lineStream.seek(lineStream.pos() + 1);
       lineStream >> x;
       lineStream.seek(lineStream.pos() + 1);
       lineStream >> y;
       lineStream.seek(lineStream.pos() + 1);
       lineStream >> z;
 
       node.coordinates = QVector3D(x, y, z);
       nodes << node;
   }
 
В регулярках я не разбирался. Быть может есть более "чистые" способы чтения? Прошу помочь.


Название: Re: Парсинг строк
Отправлено: Old от Март 09, 2016, 07:55
Написал код, который считывает строку, но выглядит он коряво на мой взгляд.
Да и работать будет только в идиальных условиях. Поставь кто пробелов и все развлится. :)
Регулярки будут сами проверять, что бы в начале была node и значения разделены запятыми.
А еще есть boost.spirit. :)


Название: Re: Парсинг строк
Отправлено: Bepec от Март 09, 2016, 08:04
А ещё есть QString::split :D


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 09, 2016, 08:29
А ещё есть QString::split :D
Ща бомбить будет, ЕВПОЧЯ :)

Спирит сложная штука, вроде. Наверное за день не разберусь. Но спасибо. А регулярками как это решить?


Название: Re: Парсинг строк
Отправлено: Old от Март 09, 2016, 08:50
А регулярками как это решить?
Я не большой специалист по регуляркам, но вот набросок (http://www.pcre.ru/eval/%5C*node%5C((%5B0-9%5D%2B)%2C(%5B0-9.-%5D%2B)%2C(%5B0-9.-%5D%2B)%2C(%5B0-9.-%5D%2B)%2C%5B0-9%5D%2B%2C%5B0-9%5D%2B%2C%5B0-9%5D%2B%2C%5B0-9%5D%2B%2C%5B0-9%5D%2B%5C)%40%3D%23%3D%40101%40%3D%23%3D%40*node(363484%2C232.7625%2C-113.996%2C40.69956%2C0%2C0%2C0%2C0%2C0)/):



Название: Re: Парсинг строк
Отправлено: gil9red от Март 09, 2016, 08:58
А ещё есть QString::split :D

А почему мы и нет? :)
Код
C++ (Qt)
std::tuple<float, float, float> parse_coords(QString text) {
   const QStringList& parts = text.replace("*node(", "").replace(")", "").split(",");
   return std::make_tuple(parts[1].toFloat(), parts[2].toFloat(), parts[3].toFloat());
}
 
QString text = "*node(363484,232.7625,-113.996,40.69956,0,0,0,0,0)";
std::tuple<float, float, float> coord = parse_coords(text);
qDebug() << std::get<0>(coord) << std::get<1>(coord) << std::get<2>(coord);
 


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 09, 2016, 11:52
gil9red, классный вариант. Здесь же можно и ликвидацию пробелов добавить и проверку на количество полученных данных.
Old, спасибо за набросок. Для меня он выглядит страшно :) так как не пользовался ранее регулярками. Думаю, пойду по пути, gil9red.


Название: Re: Парсинг строк
Отправлено: kambala от Март 09, 2016, 12:48
А регулярками как это решить?
Я не большой специалист по регуляркам, но вот набросок (http://www.pcre.ru/eval/%5C*node%5C((%5B0-9%5D%2B)%2C(%5B0-9.-%5D%2B)%2C(%5B0-9.-%5D%2B)%2C(%5B0-9.-%5D%2B)%2C%5B0-9%5D%2B%2C%5B0-9%5D%2B%2C%5B0-9%5D%2B%2C%5B0-9%5D%2B%2C%5B0-9%5D%2B%5C)%40%3D%23%3D%40101%40%3D%23%3D%40*node(363484%2C232.7625%2C-113.996%2C40.69956%2C0%2C0%2C0%2C0%2C0)/):
я бы так сделал: ссылка (http://www.pcre.ru/eval/(%5Cd%2B)%2C(-%3F%5Cd%2B(%3F%3A%5C.%5Cd%2B)%3F)%2C(-%3F%5Cd%2B(%3F%3A%5C.%5Cd%2B)%3F)%2C(-%3F%5Cd%2B(%3F%3A%5C.%5Cd%2B)%3F)%40%3D%23%3D%40001%40%3D%23%3D%40*node(363484%2C232.7625%2C-113.996%2C40.69956%2C0%2C0%2C0%2C0%2C0)//) (если в координатах дробная часть гарантирована, можно еще упростить)

можно даже не захватывать отдельные результаты, а просто взять и полное совпадение сплитнуть по ,


Название: Re: Парсинг строк
Отправлено: Igors от Март 09, 2016, 17:15
Ща бомбить будет, ЕВПОЧЯ :)
Конечно буду :) Во всех ответах прослеживается прямо какое-то "физиологическое" желание написать короче - нет, даже КАК МОЖНО КОРОЧЕ! Вот это, дескать, крутизна! :) Не самому там чего-то велосипедить, а тыц готовое - и все дела.

А давайте по-взрослому - не просто так "распарсить", а с полным контролем ошибок и предъявлением юзеру текста ошибки (и где она произошла).  
Код
C++ (Qt)
#include <QStringRef>
#include <QDebug>
 
namespace QStringParser {
 
inline QStringRef BeforeFirst( const QStringRef & src, const QString & splitter )
{
return src.left(src.indexOf(splitter));
}
 
inline QStringRef AfterFirst( const QStringRef & src, const QString & splitter )
{
int pos = src.indexOf(splitter);
iif (pos < 0) return src.right(0);
return src.mid(pos + splitter.size());
}
 
inline bool ReadInt( QStringRef & ref, const QString & splitter, int & val )
{
bool ok;
val = BeforeFirst(ref, splitter).trimmed().toInt(&ok);
if (!ok) return false;
ref = AfterFirst(ref, splitter);
return true;
}
 
inline bool ReadFloat( QStringRef & ref, const QString & splitter, float & val )
{
bool ok;
val = BeforeFirst(ref, splitter).trimmed().toFloat(&ok);
if (!ok) return false;
ref = AfterFirst(ref, splitter);
return true;
}
 
} // namespace
 
using namespace QStringParser;
 
bool Error( const QStringRef & ref, QString & err, const QString & txt )
{
err = "\nError at position " + QString::number(ref.position()) + ": " + txt +
 "\n" + ref.trimmed().toString();
return false;
}
 
bool Parse( const QString & srcStr, int & number, float val[3], QString & err )
{
static const QString prefix("node");
static const QString comma(",");
 
QStringRef src(&srcStr);
if (!src.startsWith(prefix))
return Error(src, err, "string must start with 'node'");
 
src = AfterFirst(QStringRef(&srcStr), prefix).trimmed();
if (!src.startsWith('('))
return Error(src, err, "'(' expected");
src = src.mid(1);
 
if (!ReadInt(src, comma, number))
return Error(src, err, "integer value expected");
 
for (int i = 0; i < 3; ++i)
if (!ReadFloat(src, comma, val[i]))
return Error(src, err, "float value expected");
 
return true;
}
 
int main(int argc, char **argv)
{
int nodeNum;
float val[3] = { 0 };
 
QString err;
QString test("node  ( 363484   ,232.7625,    -113.996, 40.69x956,0,0,0,0,0)");
bool ok = Parse(test, nodeNum, val, err);
qDebug() << ok << nodeNum << val[0] << val[1] << val[2];
if (!ok) qDebug() << err;
return 0;
}
Не надо бояться что (в первый раз) придется потратиться на полезные ф-ции, очень быстро это окупится


Название: Re: Парсинг строк
Отправлено: Old от Март 09, 2016, 17:34
А давайте по-взрослому - не просто так "распарсить", а с полным контролем ошибок и предъявлением юзеру текста ошибки (и где она произошла).  
В большинстве случаев это избыточно, пользователю достаточно указать только номер не валидной строки.
А в этом случае, регулярки легко и универсально позволяют как проверять корректность строки, так и получать значения. Причем они позволят разбирать гораздо более сложные случаи. В отличие от...
А если уж контроль ошибок стоит на первом месте, то лучше взять тот-же spirit.


Название: Re: Парсинг строк
Отправлено: Bepec от Март 09, 2016, 18:19
Распарсить строку и написать класс, делающий это это разные вещи.

PS преждевременная оптимизация, так помоему называется то, что демонстрирует Igors.


Название: Re: Парсинг строк
Отправлено: Igors от Март 09, 2016, 18:59
Конечно недаром придумали "регулярки", и недаром они так популярны. Да и спырыт мне очень понравился. Но "в большинстве случаев это избыточно", все можно решить куда проще написав неск ф-ций "по руке" и закинув их в утили. С оптимизацией проблем нет - ну юзаем другой Qt класс, и все. И чего ото козырять туплями, заряжать на строку целый QTextStream и.т.п ?

В общем налицо какой-то нестерпимый зуд чем-то непременно "воспользоваться", и ни в коем случае не писать самому. Если его унять - все сразу станет нормально, задачка-то детская. Просто-напросто не хватает терпения и аккуратности (после жратвы букваря)

Да, по скорости там нет желающих посравнивать?  :)



Название: Re: Парсинг строк
Отправлено: Old от Март 09, 2016, 19:12
Распарсит три числа задачка действительно детская. Были бы вместо чисел номера телефонов в любых видах или паспортные данные, вы бы велосипед еще долго писали (и не факт, что доделали бы). А пользователи регулярок уже давно бы задачу решали. :)


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 09, 2016, 22:56
Поставь кто пробелов и все развлится. :)
Как в воду глядите :) Полдня потратил на отладку из-за этого. Предполагал, что формат без пробелов.

В итоге реализовал так:

Код
C++ (Qt)
   while (!fileStream_.atEnd()){
       QString line(fileStream_.readLine());
       if (line == endNodesGroupString){
           break;
       }
 
       if (line.startsWith(nodeIndicatorString)){
           const QStringList &lineParts = line.remove(nodeIndicatorString).remove(" ").remove(")").split(',');
           constexpr int partCountRequried = 4;
           if (lineParts.count() < partCountRequried){
               qCritical() << "Got bad node line";
           }
           else{
 
               ParsedNode node;
               node.number = lineParts.at(0).toFloat();
 
               for (int k = 0; k < nodeDimension; ++k){
                   node.coordinates[k] = lineParts.at(k + 1).toFloat();
               }
               nodes << node;
           }
       }
   }
 

Пришлось заменить хранение координат с QVector3D на std::array<float, 3>.
Критикуем, улучшаем (читабельность), пожалуйста :)
Думаю, блок else заменить на метод readNode(QStringList) и  добавить к qCritical строку ошибки. Номер линии как-то лениво считать. Или есть какие средства QTextStream позволяющие выводить номер линии?


Название: Re: Парсинг строк
Отправлено: kambala от Март 10, 2016, 12:23
ага, а потом вместо пробелов попадутся табы и опять все развалится :) а в регулярках есть \s*


Название: Re: Парсинг строк
Отправлено: m_ax от Март 10, 2016, 12:28
Как по мне, так лучше потратить пару дней на изучение boost::xpressive http://www.boost.org/doc/libs/1_60_0/doc/html/xpressive.html (http://www.boost.org/doc/libs/1_60_0/doc/html/xpressive.html) или spirit, чем каждый раз лепить такое..


Название: Re: Парсинг строк
Отправлено: Igors от Март 10, 2016, 12:59
Как по мне, так лучше потратить пару дней на изучение boost::xpressive http://www.boost.org/doc/libs/1_60_0/doc/html/xpressive.html (http://www.boost.org/doc/libs/1_60_0/doc/html/xpressive.html) или spirit, чем каждый раз лепить такое..
В более общем случае - нужно что-то иметь, какие-то тулзы, чтобы не решать такие задачки всякий раз с нуля. Ну к этому пониманию надо еще прийти...


Название: Re: Парсинг строк
Отправлено: Bepec от Март 10, 2016, 13:38
После знакомства с одной прожкой в 2000 строк, которая требовала буст, питон, луа и ещё около пяти дополнительных библиотек, я стараюсь пользоваться стандартными средствами.

Конечно если их нехватает или они не дают нужный функционал, беру библиотеки, но... осторожненько.

PS мучался я с той прожкой около 2 дней. Сотни пересборок библиотек, поиск нужных их версий, раскапывание архивов в поисках уже не существующих сайтов...


Название: Re: Парсинг строк
Отправлено: Vamireh от Март 12, 2016, 17:49
Туда же. Где-то читал идею, что не надо бояться использовать велосипеды и дублирование кода (вот только не надо впадать в крайности). Типа вынесение любой повторяющейся последовательности в отдельную функцию усложняет зависимости в коде, а бездумное использование сторонних средств ведет к тому, что описал выше Верес.


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 14, 2016, 15:30
Взгляд пал на спирит.
За основу взял пример с парсингом комплексных чисел и переделал на свой лад. Прошу помочь улучшить код. Интересует прямое использование QString (как??)
Код
C++ (Qt)
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <QString>
 
#include <iostream>
#include <string>
 
constexpr int nodeDimension = 3;
struct ParsedNode{
   uint number = -1;
   std::array<float, nodeDimension> coordinates = {{-1, -1, -1}};
};
 
template <typename Iterator>
bool parse_hypermesh(Iterator first, Iterator last, ParsedNode& node)
{
   using boost::spirit::qi::uint_;
   using boost::spirit::qi::float_;
   using boost::spirit::qi::_1;
   using boost::spirit::qi::phrase_parse;
   using boost::spirit::ascii::space;
   using boost::phoenix::ref;
 
   uint nodeNumber;
   float x;
   float y;
   float z;
 
   bool result = phrase_parse(first, last,
       (
           "*node(" >> uint_[ref(nodeNumber) = _1] >> ','
           >> float_[ref(x) = _1] >> ','
           >> float_[ref(y) = _1] >> ','
           >> float_[ref(z) = _1] >> ','
           >> *(uint_ >> ',')
           >> uint_ >> ')'
       ),
       space);
 
   if (!result || first != last){
       return false;
   }
 
   node.number = nodeNumber;
   node.coordinates = {x, y, z};
   return result;
}
 
int main()
{
   const QString testString("*node( 363484 , 232.7625 , -113.996 , 40.69956 , 0 , 0 , 0 , 0 , 0 )");
 
   const std::wstring& convertedTestString = testString.toStdWString();
   ParsedNode node;
 
   if (parse_hypermesh(convertedTestString.begin(), convertedTestString.end(), node))
   {
       std::cout << "Parsing succeeded\n";
   }
   else
   {
       std::cout << "Parsing failed\n";
   }
 
   return 0;
}
 


Название: Re: Парсинг строк
Отправлено: m_ax от Март 14, 2016, 16:25
А зачем создавать в parse_hypermesh временные переменные?
Код
C++ (Qt)
uint nodeNumber;
   float x;
   float y;
   float z;
 

Можно же сразу в Node писать..

Код
C++ (Qt)
using namespace boost::spirit;
namespace phx = boost::phoenix;
namespace sw = boost::spirit::standard_wide;
 
struct Node
{
   static constexpr uint nodeDimension = 3;
   uint number = -1;
   std::array<float, nodeDimension> coordinates = {{-1, -1, -1}};
};
 
template <typename Iterator>
bool parse_hypermesh(Iterator first, Iterator last, Node& node)
{
   return qi::phrase_parse(first, last,
       (
           qi::lit("*node(") >> qi::uint_[phx::ref(node.number) = _1] >> ','
           >> qi::float_[phx::ref(node.coordinates[0]) = _1] >> ','
           >> qi::float_[phx::ref(node.coordinates[1]) = _1] >> ','
           >> qi::float_[phx::ref(node.coordinates[2]) = _1] >> ','
           >> *(qi::uint_ >> ',')
           >> qi::uint_ >> ')'
       ),
       sw::space) && first == last;
}
 
int main()
{
       std::string str = "*node( 363484 , 232.7625 , -113.996 , 40.69956 , 0 , 0 , 0 , 0 , 0 )";
 
       Node node;
 
       if (parse_hypermesh(str.begin(), str.end(), node))
       {
           std::cout << "Parsing succeeded\n";
           std::cout << node.number << std::endl;
           for (auto x : node.coordinates)
               std::cout << x << std::endl;
       }
       else
       {
           std::cout << "Parsing failed\n";
       }
   return 0;
}
 

Напрямую QString использовать  не получится.. Здесь надо писать специализацию std::char_traits для QChar.. 


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 14, 2016, 16:29
Да, я и буду писать сразу в node. Просто уподобился тому, что было в примере. Так понимаю, что временные переменные для того, чтобы не испортить исходные данные.

Здесь надо писать специализацию std::char_traits для QChar.. 
Как? Это возможно сделать вне библиотеки?

И ещё забыл сказать, что компиль сыпит предупреждения о неком включении deprecated...


Название: Re: Парсинг строк
Отправлено: m_ax от Март 14, 2016, 16:57
Цитировать
Да, я и буду писать сразу в node. Просто уподобился тому, что было в примере.
Вообще если предполагается парсить сразу список нодов, то лучше написать отдельно свою грамматику (отнаследоваться от qi::grammar). Я бы так сделал..
Тогда на выходе будет контейнер с нодами (как то так):
Код
C++ (Qt)
template <class ForwardIterator, class Skipper>
class NodeParser : public qi::grammar<ForwardIterator, Node(), Skipper>
{
...
};
 
template <class ForwardIterator, class Container>
bool node_parse(ForwardIterator begin,
                 ForwardIterator end,
                 Container & container)
{
   return qi::phrase_parse(begin, end, *NodeParser<ForwardIterator, sw::space_type>(), sw::space, container);
}
 

Цитировать
Как? Это возможно сделать вне библиотеки?
Возможно.. http://en.cppreference.com/w/cpp/string/char_traits (http://en.cppreference.com/w/cpp/string/char_traits)
Я сам этим не страдал, но теоретически прикрутить QString возможность есть..


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 14, 2016, 17:01
Про grammar пока не читал. Спасибо за пищу.
А есть ли смысл прикручивать QString? Дело в том, что я читаю сейчас из файла с помощью QFile и QTextStream. Быть может имеет смысл перейти на классы стандартной библиотеки в данном случае?


Название: Re: Парсинг строк
Отправлено: m_ax от Март 14, 2016, 17:19
Цитировать
А есть ли смысл прикручивать QString?
Я лично пока не вижу)

Цитировать
Дело в том, что я читаю сейчас из файла с помощью QFile и QTextStream. Быть может имеет смысл перейти на классы стандартной библиотеки в данном случае?
Да, можно:
Код
C++ (Qt)
typedef boost::spirit::istream_iterator istream_iterator_type;
       std::ifstream in(filename);
       in.unsetf( std::ios_base::skipws );
 
       istream_iterator_type beg( in );
       istream_iterator_type end;
       std::vector<node> container;
       node_parse(beg, end, container);
 


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 14, 2016, 17:21
Спасибо :)


Название: Re: Парсинг строк
Отправлено: Igors от Март 14, 2016, 17:34
Кстати, если уж нужна тупо "форматка", то почему не так?
Код
C++ (Qt)
#include <stdio.h>
 
int main( void )
{
 int num, last = 0;
 float val[3] = { 1, 2, 3 };
 
 const char * fmt = "node ( %d , %f, %f, %f ) %d";
 const char * test = "node ( 363484   ,232.7625,    -113.996, 40.69956)777";
//  const char * test = "node(363484,232.7625,-113.996,40.69956)777";
 
 int count = sscanf(test, fmt, &num, val, val + 1, val + 2, &last);
 printf("Ok = %d\n", int(last == 777));
 printf("%d (%f, %f, %f)\n", num, val[0], val[1], val[2]);
 
 return 0;
}
Что здесь плохо и при каких данных заклинит?


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 14, 2016, 17:37
sscanf плохо. и printf плохо.


Название: Re: Парсинг строк
Отправлено: Igors от Март 14, 2016, 17:54
sscanf плохо. и printf плохо.
А чем конкретно? Или только тем что "не модно"?  :)


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 14, 2016, 17:55
Саттер так сказал, а я ему верю. Давайте не будем начинать холивара.


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 15, 2016, 17:14
m_ax, почитал про grammar, пока что не осилить. #22 тоже плохо понимаю, но посыл ясен.
А сработает ли ваш подход если список узлов находится в середине файла и обозначен "BEGIN NODE" "END NODE"?


Название: Re: Парсинг строк
Отправлено: m_ax от Март 15, 2016, 17:22
Цитировать
m_ax, почитал про grammar, пока что не осилить. #22 тоже плохо понимаю, но посыл ясен.
Да это фактически тот же самый код, только оформленный в отдельный парсер (как qi::float_ и т.д.).

Цитировать
А сработает ли ваш подход если список узлов находится в середине файла и обозначен "BEGIN NODE" "END NODE"?
Конечно)


Название: Re: Парсинг строк
Отправлено: deMax от Март 17, 2016, 15:30
А какой объем распознаваемого текста? на мегабайтных файлах ручками получается в несколько раз быстрее.


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 17, 2016, 15:32
Объём 200-600мб


Название: Re: Парсинг строк
Отправлено: deMax от Март 17, 2016, 16:02
>>*node(363484,232.7625,-113.996,40.69956,0,0,0,0,0) - здесь первая цифра - номер узла, три следующие - его координаты.
тут 9 цифр, я наверно сделал бы так:
найти позицию скобок и запятых, потом блоки текста копировать в распознаватель QString::toDouble(). Или ручками код накидать.
Если на время наплевать, то регулярками конечно красивее.


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 17, 2016, 16:08
Я это же и сделал в #13.
С регулярками у меня всё очень плохо. Подобных парсеров надо штук 10 сделать. Пока держусь за идею со спиритом. Правда пока нет времени изучать, поэтому временно пользуюсь кодом из #13.


Название: Re: Парсинг строк
Отправлено: m_ax от Март 17, 2016, 16:17
А какой объем распознаваемого текста? на мегабайтных файлах ручками получается в несколько раз быстрее.
Спирит будет быстрее всяких "ручек") Не говоря уже о гибкости) 


Название: Re: Парсинг строк
Отправлено: deMax от Март 17, 2016, 16:26
А по времени сколько распознается? На регулярках и вашим кодом?

У вас все выражения в стиле "число(число,число,число,число,число,число,число,число)", или по сложнее что есть?


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 17, 2016, 16:56
Я регулярные не пробовал. Не знаю, как ими пользоваться. Быть может позже, интереса ради, приведу замеры спирит / регулярки / руки (мои).
Строки однотипные.


Название: Re: Парсинг строк
Отправлено: Igors от Март 18, 2016, 06:58
Код
C++ (Qt)
template <typename Iterator>
bool parse_hypermesh(Iterator first, Iterator last, Node& node)
{
   return qi::phrase_parse(first, last,
       (
           qi::lit("*node(") >> qi::uint_[phx::ref(node.number) = _1] >> ','
           >> qi::float_[phx::ref(node.coordinates[0]) = _1] >> ','
           >> qi::float_[phx::ref(node.coordinates[1]) = _1] >> ','
           >> qi::float_[phx::ref(node.coordinates[2]) = _1] >> ','
           >> *(qi::uint_ >> ',')
           >> qi::uint_ >> ')'
       ),
       sw::space) && first == last;
}
 
Зная задачу понять текст несложно. qi::float_ - читаем флот, для даблов наверное qi::double_. Правда дальше идет капитальное страхомудие с ref и _1. Это _1 я мельком видел в std, смутно помню, мол, первый аргумент (типа "слот"). Ну ладно, "это работает", когда надо - найти пример и скопи-пастить, через неск раз  запомнится. С  sw::space тоже ясно - пропускать альфы.

Как-то развить пример я не могу. Напр как выйти при обнаружении "дальше примечания" (//)? Придется надыбать примерчик и опять передрать. Какое-то ветвление в разборе - то же самое.

В общем выходит что я должен тупо и много зубрить, просто запоминать (талант которого я никогда не имел :)). Собственно это нормально, любая вещь требует какого-то запоминания базовых основ, без этого не обойтись. Вероятно переписав пяток-десяток примеров я смогу делать и более сложные вещи (несмотря на весь мудизм синтаксиса в бусте всегда есть смысл).

Ну и так ли уж это хорошо/здорово ?


Название: Re: Парсинг строк
Отправлено: Old от Март 18, 2016, 07:21
В общем выходит что я должен тупо и много зубрить, просто запоминать (талант которого я никогда не имел :)).
Поэтому, смогли зазубрить только как работать с QString, у других с этим может быть получше. Поэтому, для решения аналогичных задач, вам приходится написать несколько сот строк, а другим несколько десятков. А так да, разницы нет.


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 18, 2016, 07:49
капитальное страхомудие

не устану приводить этот кусочек кода :D
Цитировать
Код
C++ (Qt)
       switch (e->type()) {
           case QEvent::MouseButtonDblClick:
               break;
           default:
               return false;
       }
 

Да уж понятнее, что требуется от парсера, чем если делать то же самое в QString используя проверки, тримы.


Название: Re: Парсинг строк
Отправлено: Igors от Март 18, 2016, 08:07
Поэтому, для решения аналогичных задач, вам приходится написать несколько сот строк, а другим несколько десятков.
Даже если "несколько сот" и наберется - я напишу их один раз. И это будет не операторная срань, а ф-ции с нормальными именами понятными каждому без всякой подготовки (ReadInt, ReadFloat и др). А на этапе использования мой код ничуть не длиннее. Кстати за Вами контроль ошибок

И вообще позвольте спросить, почему что-то типа этого
Код
C++ (Qt)
>> qi::float_[phx::ref(node.coordinates[0]) = _1] >> ','
считается шедевром инженерной мысли? Чем мотивировано такая оценка (кроме банального "это делали умные люди")? Почему этой херней непременно нужно владеть, это типа "класс программиста" и, якобы, показатель его грамотности? А может это всего лишь один из возможных стилей - или вообще, тупиковая ветка эволюции?

...чем если делать то же самое в QString используя проверки, тримы.
Так может Вы просто не умеете это делать в QString ? :) Или вообще не умеете выделить ф-ции, построить алгоритм, а хотите только "пользоваться готовым"?


Название: Re: Парсинг строк
Отправлено: Old от Март 18, 2016, 08:14
И вообще позвольте спросить, почему что-то типа этого
Код
C++ (Qt)
>> qi::float_[phx::ref(node.coordinates[0]) = _1] >> ','
считается шедевром инженерной мысли? Чем мотивировано такая оценка (кроме банального "это делали умные люди")? Почему этой херней непременно нужно владеть, это типа "класс программиста" и, якобы, показатель его грамотности?
А откуда вы это взяли? Вас кто-то принуждает это использовать и этим владеть?

А может это всего лишь один из возможных стилей - или вообще, тупиковая ветка эволюции?
Может. Легко.


Название: Re: Парсинг строк
Отправлено: Igors от Март 18, 2016, 08:20
не устану приводить этот кусочек кода :D
Цитировать
Код
C++ (Qt)
       switch (e->type()) {
           case QEvent::MouseButtonDblClick:
               break;
           default:
               return false;
       }
 
А чего тут хихикать? Появится второе фильтруемое событие - и так написать все равно придется


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 18, 2016, 09:26
может Вы просто не умеете ... хотите только "пользоваться готовым"?
Может и не умею, может хочу.

А Вы, вот Вы... сырыми массивами пользоваться не умеете, раз используете готовенький вектор.
Аргумент?  ;)


Название: Re: Парсинг строк
Отправлено: m_ax от Март 18, 2016, 09:59
Цитировать
Как-то развить пример я не могу. Напр как выйти при обнаружении "дальше примечания" (//)? Придется надыбать примерчик и опять передрать. Какое-то ветвление в разборе - то же самое.
Вы не можете, а между тем, спирит даёт возможность (нет, это больше - это часть его идеалогии) разбить сложную задачу на простые. Можно написать свой кирпичик - парсер для node
Код
C++ (Qt)
struct Node
{
   Node() : number(0) {}
   static constexpr uint nodeDimension = 3;
   uint number;
   std::array<float, nodeDimension> coordinates = {{-1, -1, -1}};
};
 
namespace qi = boost::spirit::qi;
namespace phx = boost::phoenix;
namespace sw = boost::spirit::standard_wide;
 
 
template <class ForwardIterator>
class NodeParser : public qi::grammar<ForwardIterator, Node(), sw::space_type>
{
public:
   NodeParser() : NodeParser::base_type(start_, "NodeParser")
   {
       start_ = qi::lit("*node(")  >> qi::uint_[phx::bind(&NodeParser::set_number, qi::_val, qi::_1)] >> ','
                                   >> qi::float_[phx::bind(&NodeParser::set_x, qi::_val, qi::_1)] >> ','
                                   >> qi::float_[phx::bind(&NodeParser::set_y, qi::_val, qi::_1)] >> ','
                                   >> qi::float_[phx::bind(&NodeParser::set_z, qi::_val, qi::_1)] >> ','
                                   >> *(qi::uint_ >> ',')
                                   >> qi::uint_ >> ')';
   }
private:
   qi::rule<ForwardIterator, Node(), sw::space_type> start_;
 
   static void set_number(Node & node, uint number){ node.number = number; }
   static void set_x(Node & node, float x){ node.coordinates[0] = x; }
   static void set_y(Node & node, float y){ node.coordinates[1] = y; }
   static void set_z(Node & node, float z){ node.coordinates[2] = z; }
};
 
и потом использывать его уже в более сложных выражениях, где могут быть и всякие "примечания" и комментарии и секции и т.д.
И это правильный ООП подход.   


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 18, 2016, 10:12
m_ax, здравствуйте!
Спасибо за примерчик
Код
C++ (Qt)
struct Node
{
   Node() : number(0) {}
   static constexpr uint nodeDimension = 3;
   uint number;
   std::array<float, nodeDimension> coordinates = {{-1, -1, -1}};
};
 
namespace qi = boost::spirit::qi;
namespace phx = boost::phoenix;
namespace sw = boost::spirit::standard_wide;
 
 
template <class ForwardIterator>
class NodeParser : public qi::grammar<ForwardIterator, Node(), sw::space_type>
{
public:
   NodeParser() : NodeParser::base_type(start_, "NodeParser")
   {
       start_ = qi::lit("*node(")  >> qi::uint_[phx::bind(&NodeParser::set_number, qi::_val, qi::_1)] >> ','
                                   >> qi::float_[phx::bind(&NodeParser::set_x, qi::_val, qi::_1)] >> ','
                                   >> qi::float_[phx::bind(&NodeParser::set_y, qi::_val, qi::_1)] >> ','
                                   >> qi::float_[phx::bind(&NodeParser::set_z, qi::_val, qi::_1)] >> ','
                                   >> *(qi::uint_ >> ',')
                                   >> qi::uint_ >> ')';
   }
private:
   qi::rule<ForwardIterator, Node(), sw::space_type> start_;
 
   static void set_number(Node & node, uint number){ node.number = number; }
   static void set_x(Node & node, float x){ node.coordinates[0] = x; }
   static void set_y(Node & node, float y){ node.coordinates[1] = y; }
   static void set_z(Node & node, float z){ node.coordinates[2] = z; }
};
 
Хотел бы спросить, намеренно ли вы в Node number инициализировали в конструкторе, а coordinates в объявлении?
Почему вы объявляете методы set_xyz в парсере, а не в Node?
phx::bind может быть заменён на std::bind?


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 18, 2016, 10:14
Блин, хотел в ЛС. Получилось тут. Ну ладно...


Название: Re: Парсинг строк
Отправлено: Igors от Март 18, 2016, 12:00
.. и потом использывать его уже в более сложных выражениях, где могут быть и всякие "примечания" и комментарии и секции и т.д.
Насчет отсечки комментов не понял. На моем незатейливом велике это делается так
Код
C++ (Qt)
QStringRef src(&text);
src = BeforeFirst(src, "//");
А что здесь надо сделать чтобы достичь того же? Спасибо


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 18, 2016, 13:27
Код
C++ (Qt)
start_ = qi::lit("*node(")  >> qi::uint_[phx::bind(&NodeParser::set_number, qi::_val, qi::_1)] >> ','
                                   >> qi::float_[phx::bind(&NodeParser::set_x, qi::_val, qi::_1)] >> ','
                                   >> qi::float_[phx::bind(&NodeParser::set_y, qi::_val, qi::_1)] >> ','
                                   >> qi::float_[phx::bind(&NodeParser::set_z, qi::_val, qi::_1)] >> ','
                                   >> *(qi::uint_ >> ',')
                                   >> qi::uint_ >> ')' >> '//' >> *(qi::char_);


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 18, 2016, 13:28
Это если комментарий обязательный


Название: Re: Парсинг строк
Отправлено: Igors от Март 18, 2016, 13:43
Это если комментарий обязательный
:) Если все "обязательно", то достаточно простого sscanf

Как по мне, так лучше потратить пару дней на изучение boost::xpressive http://www.boost.org/doc/libs/1_60_0/doc/html/xpressive.html (http://www.boost.org/doc/libs/1_60_0/doc/html/xpressive.html) или spirit, чем каждый раз лепить такое..
Ну наверное "пара дней" - это для очень молодых и талантливых :) А я так вижу - не одну неделю пыль глотать


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 18, 2016, 13:47
:) Если все "обязательно", то достаточно простого sscanf
Необязательные просто в скипер засовываются.

я так вижу - не одну неделю пыль глотать

сообщение #15 m_ax Март 10
моё сообщение #19 Март 14
Уделял изучению менее часа в день. Результата достиг.


Название: Re: Парсинг строк
Отправлено: Igors от Март 18, 2016, 14:22
Необязательные просто в скипер засовываются.
А куда это ??? :) Это Ваш пост #50 ?

сообщение #15 m_ax Март 10
моё сообщение #19 Март 14
Уделял изучению менее часа в день. Результата достиг.
Ну да, прогресс налицо  :)


Название: Re: Парсинг строк
Отправлено: Old от Март 18, 2016, 14:36
А куда это ??? :)
Как скипать комментарии и обрабатывать ошибки мы с вами обсудили два года назад, когда с помощью spirit парсили bib и obj файлы.
Вы забыли? Вот ссылочка на тему, освежите. :)
http://www.prog.org.ru/topic_26894_0.html


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 18, 2016, 14:38
А куда это ??? :)

В правило. Лучше один раз почитайте документацию, чем 100 раз спрашивать  :)
Или память тренировать ;)


Название: Re: Парсинг строк
Отправлено: Igors от Март 18, 2016, 17:02
В правило. Лучше один раз почитайте документацию, чем 100 раз спрашивать  :)
Или память тренировать ;)
Ладно, обойдемся без сопливых советов :) А вот и примерчик
Цитировать
Boost C++ Libraries

"...one of the most highly regarded and expertly designed C++ library projects in the world." — Herb Sutter and Andrei Alexandrescu, C++ Coding Standards
libs/spirit/example/qi/compiler_tutorial/mini_c/skipper.hpp

/*=============================================================================
    Copyright (c) 2001-2011 Joel de Guzman

    Distributed under the Boost Software License, Version 1.0. (See accompanying
    file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
=============================================================================*/
#if !defined(BOOST_SPIRIT_MINIC_SKIPPER_HPP)
#define BOOST_SPIRIT_MINIC_SKIPPER_HPP

#include <boost/spirit/include/qi.hpp>

namespace client { namespace parser
{
    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;

    ///////////////////////////////////////////////////////////////////////////////
    //  The skipper grammar
    ///////////////////////////////////////////////////////////////////////////////
    template <typename Iterator>
    struct skipper : qi::grammar<Iterator>
    {
        skipper() : skipper::base_type(start)
        {
            qi::char_type char_;
            ascii::space_type space;

            start =
                    space                               // tab/space/cr/lf
                |   "/*" >> *(char_ - "*/") >> "*/"     // C-style comments
                ;
        }

        qi::rule<Iterator> start;
    };
}}

#endif
Значит наверное надо

Код
C++ (Qt)
start =
         space                               // tab/space/cr/lf
    |   "//" >> *(char_ - eol) >> eol     // C++ style comments
;
 
Все-таки как-то громоздко выглядит...


Название: Re: Парсинг строк
Отправлено: __Heaven__ от Март 18, 2016, 17:14
Громоздко выглядит
Код
C++ (Qt)
       switch (e->type()) {
           case QEvent::MouseButtonDblClick:
               break;
           default:
               return false;
       }
 
:D :D :D

А данный код не громоздкий точно. Быть может не привычен на фоне Qt, но привычен тем кто с РБНФ знаком.


Название: Re: Парсинг строк
Отправлено: m_ax от Март 18, 2016, 18:38
Цитировать
Хотел бы спросить, намеренно ли вы в Node number инициализировали в конструкторе, а coordinates в объявлении?
Почему вы объявляете методы set_xyz в парсере, а не в Node?
Node - просто пример. Я не знаю какие методы у вас там есть и т.д. Я просто взял то, что вы привели в качестве Node.

Методы set_XXX - это реализация сементик экшен. Их цель записать в Node (qi::_val) значение qi::_1.
Можно, например и подругому сделать через phoenix::at_c + BOOST_FUSION_ADAPT_STRUCT

Цитировать
phx::bind может быть заменён на std::bind?
Нет..


Название: Re: Парсинг строк
Отправлено: Igors от Март 19, 2016, 15:19
Посмотрел пример c комплексными числами (http://www.boost.org/doc/libs/1_44_0/libs/spirit/doc/html/spirit/qi/tutorials/complex___our_first_complex_parser.html) - ну вроде понятно. Вот хочу разобраться с номерами строк. Многие текстовики используют "если строка начинается с xxx", тогда парсить по строкам удобнее. Но тут есть мелкая пакость - может быть склейка. В том же obj файле возможен обратный слеш
Цитировать
f  1 2 \
3 4
Это должно парситься так же как и
Цитировать
f  1 2 3 4
Если грузить весь файл, то можно просто скипнуть слеш - но тогда возня с концами строк. Можно сделать извращенный аналог endsWith чтобы подсекать last и заодно заполнять флажок "склейка". Потом парсить след строку. Но тогда придется позаботиться чтобы данные склеенных строк ложились "туда же". Хмм... ну как-то явно не элегантно.

Может есть какое-то "чудесное средство"?


Название: Re: Парсинг строк
Отправлено: m_ax от Март 19, 2016, 15:53
Цитировать
Может есть какое-то "чудесное средство"?
Я, конечно, понимаю, что тема про итераторы - это больное, но..

Но можно, например, реализовать свой istream_iterator, который будет бегать по нескольким разным файлам, как по одному.

Псевдокод:
Код
C++ (Qt)
merge_istream_iterator begin({"file1.obj", "file2.obj", "file3.obj"});
merge_istream_iterator end;
 
qi::phrase_parse(begin, end, *parser, sw::space | '\' >> qi::eof, container);


Название: Re: Парсинг строк
Отправлено: Old от Март 19, 2016, 17:01
Но можно, например, реализовать свой istream_iterator, который будет бегать по нескольким разным файлам, как по одному.
Я так понял, что проблема у Igors несколько в другом: все те-же концы строк и их перенос. :)
На самом деле этой проблемы у spirit нет. Он сам независимо может считает строки и даже точные символы (для диагностики ошибок), а для реализации переноса строки через обратный слеш, достаточно просто выкусывать его из потока. Это все обсуждалось в теме два года назад.


Название: Re: Парсинг строк
Отправлено: m_ax от Март 19, 2016, 17:47
Цитировать
Я так понял, что проблема у Igors несколько в другом: все те-же концы строк и их перенос.
Возможно) Возможно даже, что через пару страниц мы всё же выясним, что он имел в виду) 


Название: Re: Парсинг строк
Отправлено: Igors от Март 20, 2016, 11:32
Возможно) Возможно даже, что через пару страниц мы всё же выясним, что он имел в виду) 
Почему до Вас все так трудно доходит? Я хочу просто-напросто читать файл по строкам. Прочитал строку - разобрал, читаю следующую и.т.д. Это выгодно с многих точек зрения. В конце-концов "текстовый файл" означает "разбит на строки". Но что тогда делать со склейкой?

На самом деле этой проблемы у spirit нет. Он сам независимо может считает строки и даже точные символы (для диагностики ошибок), а для реализации переноса строки через обратный слеш, достаточно просто выкусывать его из потока.
Грузить весь файл целиком не смотрится хорошо. Файл может быть велик, да и обновление UI и отмену придется тащить в недра парсера, где еще придется возиться с концами строк.


Название: Re: Парсинг строк
Отправлено: Old от Март 20, 2016, 13:15
Я хочу просто-напросто читать файл по строкам.
Нет никакой необходимости, читать файл по строкам, особенно в форматах, где команды могут быть многострочными.

Грузить весь файл целиком не смотрится хорошо. Файл может быть велик
Смешались в кучу кони люди...
Как соотносится spirit и чтение файла целиком?
Не читайте целиком. А Spirit с файлами не работает вообще, он работает с итераторами (уж простите что их упоминаю).

да и обновление UI и отмену придется тащить в недра парсера, где еще придется возиться с концами строк.
Не придется ничего никуда тащить, тем более обрабатывать концы строк самому. :)