1 /***************************************************************************
2                                   qgspostgresstringutils.cpp
3                               ---------------------
4     begin                : July 2019
5     copyright            : (C) 2019 by David Signer
6     email                : david at opengis dot ch
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "qgspostgresstringutils.h"
17 #include "qgsmessagelog.h"
18 
19 #include <QRegularExpression>
20 
21 #include <nlohmann/json.hpp>
22 
23 using namespace nlohmann;
24 
jumpSpace(const QString & txt,int & i)25 static void jumpSpace( const QString &txt, int &i )
26 {
27   while ( i < txt.length() && txt.at( i ).isSpace() )
28     ++i;
29 }
30 
getNextString(const QString & txt,int & i,const QString & sep)31 QString QgsPostgresStringUtils::getNextString( const QString &txt, int &i, const QString &sep )
32 {
33   jumpSpace( txt, i );
34   QString cur = txt.mid( i );
35   if ( cur.startsWith( '"' ) )
36   {
37     const QRegularExpression stringRe( QRegularExpression::anchoredPattern( "^\"((?:\\\\.|[^\"\\\\])*)\".*" ) );
38     const QRegularExpressionMatch match = stringRe.match( cur );
39     if ( !match.hasMatch() )
40     {
41       QgsMessageLog::logMessage( QObject::tr( "Cannot find end of double quoted string: %1" ).arg( txt ), QObject::tr( "PostgresStringUtils" ) );
42       return QString();
43     }
44     i += match.captured( 1 ).length() + 2;
45     jumpSpace( txt, i );
46     if ( !QStringView{txt}.mid( i ).startsWith( sep ) && i < txt.length() )
47     {
48       QgsMessageLog::logMessage( QObject::tr( "Cannot find separator: %1" ).arg( txt.mid( i ) ), QObject::tr( "PostgresStringUtils" ) );
49       return QString();
50     }
51     i += sep.length();
52     return match.captured( 1 ).replace( QLatin1String( "\\\"" ), QLatin1String( "\"" ) ).replace( QLatin1String( "\\\\" ), QLatin1String( "\\" ) );
53   }
54   else
55   {
56     int sepPos = cur.indexOf( sep );
57     if ( sepPos < 0 )
58     {
59       i += cur.length();
60       return cur.trimmed();
61     }
62     i += sepPos + sep.length();
63     return cur.left( sepPos ).trimmed();
64   }
65 }
66 
parseArray(const QString & string)67 QVariantList QgsPostgresStringUtils::parseArray( const QString &string )
68 {
69   QVariantList variantList;
70 
71   //it's a postgres array
72   QString newVal = string.mid( 1, string.length() - 2 );
73 
74   if ( newVal.trimmed().startsWith( '{' ) )
75   {
76     //it's a multidimensional array
77     QStringList values;
78     QString subarray = newVal;
79     while ( !subarray.isEmpty() )
80     {
81       bool escaped = false;
82       int openedBrackets = 1;
83       int i = 0;
84       while ( i < subarray.length()  && openedBrackets > 0 )
85       {
86         ++i;
87 
88         if ( subarray.at( i ) == '}' && !escaped ) openedBrackets--;
89         else if ( subarray.at( i ) == '{' && !escaped ) openedBrackets++;
90 
91         escaped = !escaped ? subarray.at( i ) == '\\' : false;
92       }
93 
94       variantList.append( subarray.left( ++i ) );
95       i = subarray.indexOf( ',', i );
96       i = i > 0 ? subarray.indexOf( '{', i ) : -1;
97       if ( i == -1 )
98         break;
99 
100       subarray = subarray.mid( i );
101     }
102   }
103   else
104   {
105     int i = 0;
106     while ( i < newVal.length() )
107     {
108       const QString value = getNextString( newVal, i, QStringLiteral( "," ) );
109       if ( value.isNull() )
110       {
111         QgsMessageLog::logMessage( QObject::tr( "Error parsing PG like array: %1" ).arg( newVal ), QObject::tr( "PostgresStringUtils" ) );
112         break;
113       }
114       variantList.append( value );
115     }
116   }
117 
118   return variantList;
119 
120 }
121 
buildArray(const QVariantList & list)122 QString QgsPostgresStringUtils::buildArray( const QVariantList &list )
123 {
124   QStringList sl;
125   for ( const QVariant &v : std::as_const( list ) )
126   {
127     // Convert to proper type
128     switch ( v.type() )
129     {
130       case QVariant::Type::Int:
131       case QVariant::Type::LongLong:
132         sl.push_back( v.toString() );
133         break;
134       default:
135         QString newS = v.toString();
136         if ( newS.startsWith( '{' ) )
137         {
138           sl.push_back( newS );
139         }
140         else
141         {
142           newS.replace( '\\', QLatin1String( R"(\\)" ) );
143           newS.replace( '\"', QLatin1String( R"(\")" ) );
144           sl.push_back( "\"" + newS + "\"" );
145         }
146         break;
147     }
148   }
149   //store as a formatted string because the fields supports only string
150   QString s = sl.join( ',' ).prepend( '{' ).append( '}' );
151 
152   return s;
153 }
154