mirror of
https://github.com/levinsv/pgadmin3.git
synced 2026-05-15 14:15:49 -06:00
1. Сортировка колонок на вкладках Статистика сохряняется по возможности. 2. Узлы плана которые помечены как (never executed) не подсвечиваются. 3. При построении плана всегда добавляется опция "SUMMARY on" 4. Исправлено не корректное отображение зависимостей для таблиц из публикаций. 5. В отчетах о статистике добавлена итоговая информация по таблицам отчета.
1456 lines
30 KiB
C++
1456 lines
30 KiB
C++
//////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// pgAdmin III - PostgreSQL Tools
|
|
//
|
|
// Copyright (C) 2002 - 2016, The pgAdmin Development Team
|
|
// This software is released under the PostgreSQL Licence
|
|
//
|
|
// misc.cpp - Miscellaneous Utilities
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "pgAdmin3.h"
|
|
|
|
// wxWindows headers
|
|
#include <wx/wx.h>
|
|
#include <wx/app.h>
|
|
#include <wx/timer.h>
|
|
#include <wx/xrc/xmlres.h>
|
|
#include <wx/dir.h>
|
|
#include <wx/file.h>
|
|
#include <wx/textbuf.h>
|
|
#include <wx/help.h>
|
|
#include <wx/html/helpctrl.h>
|
|
#include <wx/fontenc.h>
|
|
#include <wx/display.h>
|
|
|
|
#include "utils/utffile.h"
|
|
#include <locale.h>
|
|
|
|
#ifdef __WXMSW__
|
|
#include <wx/msw/helpchm.h>
|
|
#endif
|
|
|
|
// Standard headers
|
|
#include <stdlib.h>
|
|
|
|
// App headers
|
|
#include "utils/misc.h"
|
|
|
|
#if !defined(PGSCLI)
|
|
|
|
// App headers
|
|
#include "frm/frmMain.h"
|
|
|
|
wxImageList *imageList = 0;
|
|
|
|
#endif // PGSCLI
|
|
|
|
extern "C"
|
|
{
|
|
#define YYSTYPE_IS_DECLARED
|
|
#define DECIMAL DECIMAL_P
|
|
typedef int YYSTYPE;
|
|
#include "parser/keywords.h"
|
|
}
|
|
|
|
// we dont have an appropriate wxLongLong method
|
|
#ifdef __WIN32__
|
|
#define atolonglong _atoi64
|
|
#else
|
|
#ifdef __WXMAC__
|
|
#define atolonglong(str) strtoll(str, (char **)NULL, 10)
|
|
#else
|
|
#ifdef __FreeBSD__
|
|
#define atolonglong(str) strtoll(str, (char **)NULL, 10)
|
|
#else
|
|
#define atolonglong atoll
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
// Conversions
|
|
|
|
|
|
wxString BoolToYesNo(bool value)
|
|
{
|
|
return value ? _("Yes") : _("No");
|
|
}
|
|
|
|
|
|
wxString BoolToStr(bool value)
|
|
{
|
|
return value ? wxT("true") : wxT("false");
|
|
}
|
|
|
|
wxString escapeHtml(wxString text, bool pre) {
|
|
text.Replace("&", "&");
|
|
text.Replace("<", "<");
|
|
text.Replace(">", ">");
|
|
if (!pre) text.Replace("\n", "¶<br>");
|
|
return text;
|
|
}
|
|
|
|
|
|
bool StrToBool(const wxString &value)
|
|
{
|
|
if (value.StartsWith(wxT("t")))
|
|
{
|
|
return true;
|
|
}
|
|
else if (value.StartsWith(wxT("T")))
|
|
{
|
|
return true;
|
|
}
|
|
else if (value.StartsWith(wxT("1")))
|
|
{
|
|
return true;
|
|
}
|
|
else if (value.StartsWith(wxT("Y")))
|
|
{
|
|
return true;
|
|
}
|
|
else if (value.StartsWith(wxT("y")))
|
|
{
|
|
return true;
|
|
}
|
|
else if (value == wxT("on"))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
wxString NumToStr(long value)
|
|
{
|
|
wxString result;
|
|
result.Printf(wxT("%ld"), value);
|
|
return result;
|
|
}
|
|
|
|
|
|
wxString NumToStr(OID value)
|
|
{
|
|
wxString result;
|
|
result.Printf(wxT("%lu"), (long)value);
|
|
return result;
|
|
}
|
|
|
|
|
|
long StrToLong(const wxString &value)
|
|
{
|
|
return atol(value.ToAscii());
|
|
}
|
|
|
|
|
|
OID StrToOid(const wxString &value)
|
|
{
|
|
return (OID)strtoul(value.ToAscii(), 0, 10);
|
|
}
|
|
|
|
wxString generate_spaces(int length)
|
|
{
|
|
return wxString().Pad(length);
|
|
}
|
|
|
|
wxString NumToStr(double value)
|
|
{
|
|
wxString result;
|
|
static wxString decsep;
|
|
|
|
if (decsep.Length() == 0)
|
|
{
|
|
decsep.Printf(wxT("%lf"), 1.2);
|
|
decsep = decsep[(unsigned int)1];
|
|
}
|
|
|
|
result.Printf(wxT("%lf"), value);
|
|
result.Replace(decsep, wxT("."));
|
|
|
|
// Get rid of excessive decimal places
|
|
if (result.Contains(wxT(".")))
|
|
while (result.Right(1) == wxT("0"))
|
|
result.RemoveLast();
|
|
if (result.Right(1) == wxT("."))
|
|
result.RemoveLast();
|
|
|
|
return result;
|
|
}
|
|
wxString NumToStrHuman(wxLongLong value) {
|
|
// billion
|
|
long ddiv = 1000000000;
|
|
wxLongLong r = 0;
|
|
wxString s;
|
|
while ((r=value/ddiv)==0) {
|
|
ddiv = ddiv / 1000;
|
|
if (ddiv == 0) return wxEmptyString;
|
|
}
|
|
if (ddiv == 1000000000) s = "Bi";
|
|
else if (ddiv == 1000000) s = "Mi";
|
|
else if (ddiv == 1000) s = "ths";
|
|
else return wxEmptyString;
|
|
|
|
wxLongLong m = value % ddiv;
|
|
m = m / (ddiv / 10);
|
|
wxString rez;
|
|
rez = NumToStr(r) + (m == 0 ? "": "." + NumToStr(m))+" "+s;
|
|
|
|
return rez;
|
|
}
|
|
|
|
wxString NumToStr(wxLongLong value)
|
|
{
|
|
wxString str;
|
|
#if wxCHECK_VERSION(2, 9, 0)
|
|
str.Printf("%" wxLongLongFmtSpec "d", value.GetValue());
|
|
#else
|
|
str.Printf(wxT("%") wxLongLongFmtSpec wxT("d"), value.GetValue());
|
|
#endif
|
|
return str;
|
|
}
|
|
|
|
|
|
double StrToDouble(const wxString &value)
|
|
{
|
|
wxCharBuffer buf = value.ToAscii();
|
|
char *p = (char *)strchr(buf, '.');
|
|
if (p)
|
|
*p = localeconv()->decimal_point[0];
|
|
|
|
return strtod(buf, 0);
|
|
}
|
|
|
|
|
|
wxLongLong StrToLongLong(const wxString &value)
|
|
{
|
|
return atolonglong(value.ToAscii());
|
|
}
|
|
|
|
|
|
wxString DateToAnsiStr(const wxDateTime &datetime)
|
|
{
|
|
if (!datetime.IsValid())
|
|
return wxEmptyString;
|
|
|
|
return datetime.FormatISODate() + wxT(" ") + datetime.FormatISOTime();
|
|
}
|
|
|
|
|
|
wxString DateToStr(const wxDateTime &datetime)
|
|
{
|
|
if (!datetime.IsValid())
|
|
return wxEmptyString;
|
|
|
|
return datetime.FormatDate() + wxT(" ") + datetime.FormatTime();
|
|
}
|
|
|
|
wxString ContrastColorBlackOrWhite(wxColour &bgColor) {
|
|
float k = bgColor.GetRed() * 0.299 + bgColor.GetGreen() * 0.587 + bgColor.GetBlue() * 0.114;
|
|
if (k <= 150) return "#FFFFFF"; else return "#000000";
|
|
}
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="sourcestringsize"> NN MB , N bytes, N kB, N GB</param>
|
|
/// <returns> size MB</returns>
|
|
double ConvertSizeToMB(const wxString& sourcestringsize) {
|
|
double d = 0;
|
|
if (sourcestringsize.ToCDouble(&d)) {
|
|
d = 0;
|
|
}
|
|
else {
|
|
if (sourcestringsize.Right(2) == "kB") d = d / 1024; //kB => MB
|
|
else if (sourcestringsize.Right(2) == "GB") d = d * 1024;//GB => MB
|
|
else if (sourcestringsize.Right(2) == "TB") d = d * 1024 * 1024;//TB => MB
|
|
else if (sourcestringsize.Right(5) == "bytes") d = d / 1024 / 1024; //bytes => MB
|
|
}
|
|
return d;
|
|
}
|
|
wxString ElapsedTimeToStr(wxLongLong msec)
|
|
{
|
|
wxTimeSpan tsMsec(0, 0, 0, msec);
|
|
|
|
int days = tsMsec.GetDays();
|
|
int hours = (wxTimeSpan(tsMsec.GetHours(), 0, 0, 0) - wxTimeSpan(days * 24)).GetHours();
|
|
int minutes = (wxTimeSpan(0, tsMsec.GetMinutes(), 0, 0) - wxTimeSpan(hours)).GetMinutes();
|
|
long seconds = (wxTimeSpan(0, 0, tsMsec.GetSeconds(), 0) - wxTimeSpan(hours, minutes)).GetSeconds().ToLong();
|
|
long milliseconds = (wxTimeSpan(0, 0, 0, tsMsec.GetMilliseconds()) - wxTimeSpan(0, 0, seconds)).GetMilliseconds().ToLong();
|
|
|
|
if (days > 0)
|
|
return wxString::Format(
|
|
wxT("%d %s, %02d:%02d:%02ld hours"),
|
|
days, wxT("days"), hours, minutes, seconds
|
|
);
|
|
else if (hours > 0)
|
|
return wxString::Format(
|
|
wxT("%02d:%02d:%02ld hours"), hours, minutes, seconds
|
|
);
|
|
else if (msec >= 1000 * 60)
|
|
return wxString::Format(wxT("%02d:%02ld minutes"), minutes, seconds);
|
|
else if (msec >= 1000)
|
|
return wxString::Format(
|
|
wxT("%ld.%ld secs"), seconds, milliseconds / 100
|
|
);
|
|
else
|
|
return msec.ToString() + wxT(" msec");
|
|
}
|
|
|
|
wxDateTime StrToDateTime(const wxString &value)
|
|
{
|
|
wxDateTime dt;
|
|
/* This hasn't just been used. ( Is not infinity ) */
|
|
if ( !value.IsEmpty() )
|
|
dt.ParseDateTime(value);
|
|
return dt;
|
|
}
|
|
|
|
#if !defined(PGSCLI)
|
|
|
|
void CheckOnScreen(wxWindow *win, wxPoint &pos, wxSize &size, const int w0, const int h0)
|
|
{
|
|
wxRect rect;
|
|
int scrH, scrW;
|
|
|
|
wxSize screenSize = wxGetDisplaySize();
|
|
scrW = screenSize.x;
|
|
scrH = screenSize.y;
|
|
|
|
if (pos.x > scrW - w0)
|
|
pos.x = scrW - w0;
|
|
if (pos.y > scrH - h0)
|
|
pos.y = scrH - h0;
|
|
|
|
if (pos.x < 0)
|
|
pos.x = 0;
|
|
if (pos.y < 0)
|
|
pos.y = 0;
|
|
|
|
if (size.GetWidth() < w0)
|
|
size.SetWidth(w0);
|
|
if (size.GetHeight() < h0)
|
|
size.SetHeight(h0);
|
|
|
|
if (size.GetWidth() > scrW)
|
|
size.SetWidth(scrW);
|
|
if (size.GetHeight() > scrH)
|
|
size.SetHeight(scrH);
|
|
}
|
|
|
|
#endif // PGSCLI
|
|
|
|
wxString qtConnString(const wxString &value)
|
|
{
|
|
wxString result = value;
|
|
|
|
result.Replace(wxT("\\"), wxT("\\\\"));
|
|
result.Replace(wxT("'"), wxT("\\'"));
|
|
result.Append(wxT("'"));
|
|
result.Prepend(wxT("'"));
|
|
|
|
return result;
|
|
}
|
|
|
|
#if !defined(PGSCLI)
|
|
|
|
wxString IdAndName(long id, const wxString &name)
|
|
{
|
|
wxString str;
|
|
str.Printf(wxT("%ld - %s"), id, name.BeforeFirst('\n').c_str());
|
|
return str;
|
|
}
|
|
|
|
|
|
wxString qtDbStringDollar(const wxString &value)
|
|
{
|
|
wxString qtDefault = wxT("BODY");
|
|
wxString qt = qtDefault;
|
|
int counter = 1;
|
|
if (value.Find('\'') < 0 && value.Find('\n') < 0 && value.Find('\r') < 0)
|
|
{
|
|
wxString ret = value;
|
|
ret.Replace(wxT("\\"), wxT("\\\\"));
|
|
ret.Replace(wxT("'"), wxT("''"));
|
|
ret.Append(wxT("'"));
|
|
ret.Prepend(wxT("'"));
|
|
return ret;
|
|
}
|
|
|
|
while (value.Find(wxT("$") + qt + wxT("$")) >= 0)
|
|
qt.Printf(wxT("%s%d"), qtDefault.c_str(), counter++);
|
|
|
|
|
|
return wxT("$") + qt + wxT("$")
|
|
+ value
|
|
+ wxT("$") + qt + wxT("$");
|
|
}
|
|
|
|
|
|
void FillKeywords(wxString &str)
|
|
{
|
|
// unfortunately, the keyword list is static.
|
|
int i;
|
|
|
|
str = wxString();
|
|
|
|
for (i = 0; i < NumScanKeywords; i++)
|
|
{
|
|
str += wxT(" ") + wxString::FromAscii(ScanKeywords[i].name);
|
|
}
|
|
for (i = 0; i < NumScanKeywordsExtra; i++)
|
|
{
|
|
str += wxT(" ") + wxString::FromAscii(ScanKeywordsExtra[i].name);
|
|
}
|
|
}
|
|
|
|
#endif // PGSCLI
|
|
|
|
static bool needsQuoting(wxString &value, bool forTypes)
|
|
{
|
|
// Is it a number?
|
|
if (value.IsNumber())
|
|
return true;
|
|
else
|
|
{
|
|
// certain types should not be quoted even though it contains a space. Evilness.
|
|
wxString valNoArray;
|
|
if (forTypes && value.Right(2) == wxT("[]"))
|
|
valNoArray = value.Mid(0, value.Len() - 2);
|
|
else
|
|
valNoArray = value;
|
|
|
|
if (forTypes &&
|
|
(!valNoArray.CmpNoCase(wxT("character varying")) ||
|
|
!valNoArray.CmpNoCase(wxT("\"char\"")) ||
|
|
!valNoArray.CmpNoCase(wxT("bit varying")) ||
|
|
!valNoArray.CmpNoCase(wxT("double precision")) ||
|
|
!valNoArray.CmpNoCase(wxT("timestamp without time zone")) ||
|
|
!valNoArray.CmpNoCase(wxT("timestamp with time zone")) ||
|
|
!valNoArray.CmpNoCase(wxT("time without time zone")) ||
|
|
!valNoArray.CmpNoCase(wxT("time with time zone")) ||
|
|
!valNoArray.CmpNoCase(wxT("\"trigger\"")) ||
|
|
!valNoArray.CmpNoCase(wxT("\"unknown\""))))
|
|
return false;
|
|
|
|
int pos = 0;
|
|
while (pos < (int)valNoArray.length())
|
|
{
|
|
wxChar c = valNoArray.GetChar(pos);
|
|
if (!((pos > 0) && (c >= '0' && c <= '9')) &&
|
|
!(c >= 'a' && c <= 'z') &&
|
|
!(c == '_'))
|
|
{
|
|
return true;
|
|
}
|
|
pos++;
|
|
}
|
|
}
|
|
|
|
// is it a keyword?
|
|
const ScanKeyword *sk = ScanKeywordLookup(value.ToAscii());
|
|
if (!sk)
|
|
return false;
|
|
if (sk->category == UNRESERVED_KEYWORD)
|
|
return false;
|
|
if (forTypes && sk->category == COL_NAME_KEYWORD)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
wxString qtTypeIdent(const wxString &value)
|
|
{
|
|
if (value.Length() == 0)
|
|
return value;
|
|
|
|
wxString result = value;
|
|
|
|
if (needsQuoting(result, true))
|
|
{
|
|
result.Replace(wxT("\""), wxT("\"\""));
|
|
return wxT("\"") + result + wxT("\"");
|
|
}
|
|
else
|
|
return result;
|
|
}
|
|
|
|
|
|
wxString qtIdent(const wxString &value)
|
|
{
|
|
if (value.Length() == 0)
|
|
return value;
|
|
|
|
wxString result = value;
|
|
|
|
if (needsQuoting(result, false))
|
|
{
|
|
result.Replace(wxT("\""), wxT("\"\""));
|
|
return wxT("\"") + result + wxT("\"");
|
|
}
|
|
else
|
|
return result;
|
|
}
|
|
|
|
#if !defined(PGSCLI)
|
|
|
|
wxString qtStrip(const wxString &str)
|
|
{
|
|
if (str.Left(2) == wxT("\\\""))
|
|
return str.Mid(2, str.Length() - 4);
|
|
else
|
|
return str;
|
|
}
|
|
|
|
|
|
wxString TransformToNewDatconfig(const wxString &list)
|
|
{
|
|
const wxChar *cp = list.c_str();
|
|
|
|
wxString config = wxEmptyString;
|
|
wxString str = wxEmptyString;
|
|
bool quote = false;
|
|
|
|
while (*cp)
|
|
{
|
|
switch (*cp)
|
|
{
|
|
case '\\':
|
|
{
|
|
if (cp[1] == '"')
|
|
{
|
|
str.Append(*cp);
|
|
cp++;
|
|
str.Append(*cp);
|
|
}
|
|
break;
|
|
}
|
|
case '"':
|
|
case '\'':
|
|
{
|
|
quote = !quote;
|
|
str.Append(*cp);
|
|
break;
|
|
}
|
|
case ',':
|
|
{
|
|
if (!quote)
|
|
{
|
|
if (!config.IsEmpty())
|
|
config += wxT(",");
|
|
config += wxT("=") + str;
|
|
str = wxEmptyString;
|
|
break;
|
|
}
|
|
}
|
|
default:
|
|
{
|
|
str.Append(*cp);
|
|
break;
|
|
}
|
|
}
|
|
cp++;
|
|
}
|
|
|
|
if (!str.IsEmpty())
|
|
{
|
|
if (!config.IsEmpty())
|
|
config += wxT(",");
|
|
config += wxT("=") + str;
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
|
|
void FillArray(wxArrayString &array, const wxString &list)
|
|
{
|
|
const wxChar *cp = list.c_str();
|
|
|
|
wxString str;
|
|
bool quote = false;
|
|
|
|
while (*cp)
|
|
{
|
|
switch (*cp)
|
|
{
|
|
case '\\':
|
|
{
|
|
if (cp[1] == '"')
|
|
{
|
|
cp++;
|
|
str.Append(*cp);
|
|
}
|
|
break;
|
|
}
|
|
case '"':
|
|
case '\'':
|
|
{
|
|
quote = !quote;
|
|
break;
|
|
}
|
|
case ',':
|
|
{
|
|
if (!quote)
|
|
{
|
|
array.Add(str);
|
|
str = wxEmptyString;
|
|
break;
|
|
}
|
|
}
|
|
default:
|
|
{
|
|
str.Append(*cp);
|
|
break;
|
|
}
|
|
}
|
|
cp++;
|
|
}
|
|
if (!str.IsEmpty())
|
|
array.Add(str);
|
|
}
|
|
|
|
|
|
queryTokenizer::queryTokenizer(const wxString &str, const wxChar delim)
|
|
: wxStringTokenizer()
|
|
{
|
|
if (delim == (wxChar)' ')
|
|
SetString(str, wxT(" \n\r\t"), wxTOKEN_RET_EMPTY_ALL);
|
|
else
|
|
SetString(str, delim, wxTOKEN_RET_EMPTY_ALL);
|
|
delimiter = delim;
|
|
}
|
|
|
|
|
|
void AppendIfFilled(wxString &str, const wxString &delimiter, const wxString &what)
|
|
{
|
|
if (!what.IsNull())
|
|
str += delimiter + what;
|
|
}
|
|
|
|
|
|
wxString queryTokenizer::GetNextToken()
|
|
{
|
|
// we need to override wxStringTokenizer, because we have to handle quotes
|
|
wxString str;
|
|
|
|
bool foundQuote = false;
|
|
do
|
|
{
|
|
wxString s = wxStringTokenizer::GetNextToken();
|
|
str.Append(s);
|
|
int quotePos;
|
|
do
|
|
{
|
|
quotePos = s.Find('"');
|
|
if (quotePos >= 0)
|
|
{
|
|
foundQuote = !foundQuote;
|
|
s = s.Mid(quotePos + 1);
|
|
}
|
|
}
|
|
while (quotePos >= 0);
|
|
|
|
if (foundQuote)
|
|
str.Append(delimiter);
|
|
}
|
|
while (foundQuote && HasMoreTokens());
|
|
|
|
return str;
|
|
}
|
|
|
|
|
|
wxString FileRead(const wxString &filename, int format)
|
|
{
|
|
wxString str;
|
|
|
|
wxFontEncoding encoding;
|
|
if (format > 0)
|
|
encoding = wxFONTENCODING_UTF8;
|
|
else
|
|
encoding = wxFONTENCODING_DEFAULT;
|
|
|
|
wxUtfFile file(filename, wxFile::read, encoding);
|
|
|
|
if (file.IsOpened())
|
|
file.Read(str);
|
|
|
|
return wxTextBuffer::Translate(str, wxTextFileType_Unix); // we want only \n
|
|
}
|
|
|
|
|
|
bool FileWrite(const wxString &filename, const wxString &data, int format)
|
|
{
|
|
wxFontEncoding encoding = wxFONTENCODING_DEFAULT;
|
|
wxUtfFile file;
|
|
|
|
if (format < 0)
|
|
{
|
|
if (wxFile::Access(filename, wxFile::read))
|
|
{
|
|
file.Open(filename);
|
|
encoding = file.GetEncoding();
|
|
file.Close();
|
|
}
|
|
if (encoding == wxFONTENCODING_DEFAULT)
|
|
encoding = settings->GetUnicodeFile() ? wxFONTENCODING_UTF8 : wxFONTENCODING_SYSTEM;
|
|
}
|
|
else
|
|
encoding = format ? wxFONTENCODING_UTF8 : wxFONTENCODING_SYSTEM;
|
|
|
|
|
|
file.Open(filename, wxFile::write, wxS_DEFAULT, encoding);
|
|
|
|
if (file.IsOpened())
|
|
return file.Write(wxTextBuffer::Translate(data));
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
wxString CleanHelpPath(const wxString &path)
|
|
{
|
|
wxString thePath = path;
|
|
|
|
if (thePath.IsEmpty())
|
|
return wxEmptyString;
|
|
|
|
// If the filename ends in .chm, .hhp, .pdf or .zip, it's
|
|
// a file based help system and we can assumes it's
|
|
// correct.
|
|
if (thePath.Lower().EndsWith(wxT(".hhp")) ||
|
|
#if defined (__WXMSW__) || wxUSE_LIBMSPACK
|
|
thePath.Lower().EndsWith(wxT(".chm")) ||
|
|
#endif
|
|
thePath.Lower().EndsWith(wxT(".pdf")) ||
|
|
thePath.Lower().EndsWith(wxT(".zip")))
|
|
return thePath;
|
|
|
|
// In all other cases we must have a directory
|
|
wxString sep;
|
|
|
|
// Figure out the appropriate seperator
|
|
if (thePath.Lower().Contains(wxT("://")))
|
|
sep = wxT("/");
|
|
else
|
|
sep = wxFileName::GetPathSeparator();
|
|
|
|
// First, correct the path separators
|
|
thePath.Replace(wxT("/"), sep);
|
|
thePath.Replace(wxT("\\"), sep);
|
|
|
|
// Now, append the seperator if it's not there
|
|
if (!thePath.EndsWith(sep))
|
|
thePath += sep;
|
|
|
|
return thePath;
|
|
}
|
|
|
|
bool HelpPathValid(const wxString &path)
|
|
{
|
|
if (path.IsEmpty())
|
|
return true;
|
|
|
|
// If the filename contains any of the sensible URL schemes
|
|
// we just assume it's correct if the end also looks right.
|
|
if ((path.Lower().StartsWith(wxT("http://")) ||
|
|
path.Lower().StartsWith(wxT("https://")) ||
|
|
path.Lower().StartsWith(wxT("file://")) ||
|
|
path.Lower().StartsWith(wxT("ftp://"))) &&
|
|
(path.Lower().EndsWith(wxT(".hhp")) ||
|
|
#if defined (__WXMSW__) || wxUSE_LIBMSPACK
|
|
path.Lower().EndsWith(wxT(".chm")) ||
|
|
#endif
|
|
path.Lower().EndsWith(wxT(".pdf")) ||
|
|
path.Lower().EndsWith(wxT(".zip")) ||
|
|
path.Lower().EndsWith(wxT("/"))))
|
|
return true;
|
|
|
|
// Otherwise, we're looking for a file (or directory) that
|
|
// actually exists.
|
|
wxString sep = wxFileName::GetPathSeparator();
|
|
if (path.Lower().EndsWith(wxT(".hhp")) ||
|
|
#if defined (__WXMSW__) || wxUSE_LIBMSPACK
|
|
path.Lower().EndsWith(wxT(".chm")) ||
|
|
#endif
|
|
path.Lower().EndsWith(wxT(".pdf")) ||
|
|
path.Lower().EndsWith(wxT(".zip")))
|
|
return wxFile::Exists(path);
|
|
else if (path.Lower().EndsWith(sep))
|
|
return wxDir::Exists(path);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void DisplayHelp(const wxString &helpTopic, const HelpType helpType)
|
|
{
|
|
static wxHelpControllerBase *pgHelpCtl = 0;
|
|
static wxHelpControllerBase *edbHelpCtl = 0;
|
|
static wxHelpControllerBase *greenplumHelpCtl = 0;
|
|
static wxHelpControllerBase *slonyHelpCtl = 0;
|
|
static wxString pgInitPath = wxEmptyString;
|
|
static wxString edbInitPath = wxEmptyString;
|
|
static wxString gpInitPath = wxEmptyString;
|
|
static wxString slonyInitPath = wxEmptyString;
|
|
|
|
switch (helpType)
|
|
{
|
|
case HELP_PGADMIN:
|
|
DisplayPgAdminHelp(helpTopic);
|
|
break;
|
|
|
|
case HELP_POSTGRESQL:
|
|
DisplayExternalHelp(helpTopic, settings->GetPgHelpPath(), pgHelpCtl, (pgInitPath != settings->GetPgHelpPath() ? true : false));
|
|
pgInitPath = settings->GetPgHelpPath();
|
|
break;
|
|
|
|
case HELP_ENTERPRISEDB:
|
|
DisplayExternalHelp(helpTopic, settings->GetEdbHelpPath(), edbHelpCtl, (edbInitPath != settings->GetEdbHelpPath() ? true : false));
|
|
edbInitPath = settings->GetEdbHelpPath();
|
|
break;
|
|
|
|
case HELP_GREENPLUM:
|
|
{
|
|
// the old help path (stored in the settings) is no longer working
|
|
static wxString gpHelpPath = settings->GetGpHelpPath();
|
|
|
|
// Note: never end the URL on "index.html"
|
|
// InitHelp() does obscure magic with this ending
|
|
if (gpHelpPath.CmpNoCase(wxT("http://docs.gopivotal.com/gpdb/")) == 0)
|
|
{
|
|
gpHelpPath = wxT("http://gpdb.docs.pivotal.io/");
|
|
// Replace the path to the old domain with the link to
|
|
// the new documentation path
|
|
// The old link is working for now, but there is no guarantee
|
|
// that it will stay this way
|
|
// Also the new link automatically redirects to the latest version
|
|
settings->SetGpHelpPath(gpHelpPath);
|
|
}
|
|
|
|
if (gpHelpPath.CmpNoCase(wxT("http://www.greenplum.com/docs/3300/")) == 0)
|
|
{
|
|
gpHelpPath = wxT("http://gpdb.docs.pivotal.io/");
|
|
// this is the old link, update the link to the new documentation link
|
|
// problem: this saves the link into the configuration file
|
|
settings->SetGpHelpPath(gpHelpPath);
|
|
}
|
|
DisplayExternalHelp(helpTopic, settings->GetGpHelpPath(), greenplumHelpCtl, (gpInitPath != settings->GetGpHelpPath() ? true : false));
|
|
gpInitPath = settings->GetGpHelpPath();
|
|
}
|
|
break;
|
|
|
|
case HELP_SLONY:
|
|
DisplayExternalHelp(helpTopic, settings->GetSlonyHelpPath(), slonyHelpCtl, (slonyInitPath != settings->GetSlonyHelpPath() ? true : false));
|
|
slonyInitPath = settings->GetSlonyHelpPath();
|
|
break;
|
|
|
|
default:
|
|
DisplayPgAdminHelp(helpTopic);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void DisplayPgAdminHelp(const wxString &helpTopic)
|
|
{
|
|
static wxHelpControllerBase *helpCtl = 0;
|
|
static bool firstCall = true;
|
|
|
|
// Startup the main help system
|
|
if (firstCall)
|
|
{
|
|
firstCall = false;
|
|
wxString helpdir = docPath + wxT("/") + settings->GetCanonicalLanguageName();
|
|
|
|
if (!wxFile::Exists(helpdir + wxT("/pgadmin3.hhp")) &&
|
|
#if defined(__WXMSW__) || wxUSE_LIBMSPACK
|
|
!wxFile::Exists(helpdir + wxT("/pgadmin3.chm")) &&
|
|
#endif
|
|
!wxFile::Exists(helpdir + wxT("/pgadmin3.zip")))
|
|
helpdir = docPath + wxT("/en_US");
|
|
|
|
#ifdef __WXMSW__
|
|
#ifndef __WXDEBUG__
|
|
if (wxFile::Exists(helpdir + wxT("/pgadmin3.chm")))
|
|
{
|
|
helpCtl = new wxCHMHelpController();
|
|
helpCtl->Initialize(helpdir + wxT("/pgadmin3"));
|
|
}
|
|
else
|
|
#endif
|
|
#endif
|
|
#if wxUSE_LIBMSPACK
|
|
if (wxFile::Exists(helpdir + wxT("/pgadmin3.chm")) ||
|
|
wxFile::Exists(helpdir + wxT("/pgadmin3.hhp")) || wxFile::Exists(helpdir + wxT("/pgadmin3.zip")))
|
|
#else
|
|
if (wxFile::Exists(helpdir + wxT("/pgadmin3.hhp")) || wxFile::Exists(helpdir + wxT("/pgadmin3.zip")))
|
|
#endif
|
|
{
|
|
helpCtl = new wxHtmlHelpController();
|
|
helpCtl->Initialize(helpdir + wxT("/pgadmin3"));
|
|
}
|
|
}
|
|
|
|
wxString page;
|
|
int hashPos = helpTopic.Find('#');
|
|
if (hashPos < 0)
|
|
page = helpTopic + wxT(".html");
|
|
else
|
|
page = helpTopic.Left(hashPos) + wxT(".html") + helpTopic.Mid(hashPos);
|
|
|
|
if (helpCtl)
|
|
{
|
|
if (helpTopic == wxT("index.html"))
|
|
helpCtl->DisplayContents();
|
|
else
|
|
helpCtl->DisplaySection(page);
|
|
}
|
|
else
|
|
{
|
|
wxLaunchDefaultBrowser(page);
|
|
}
|
|
}
|
|
|
|
void DisplayExternalHelp(const wxString &helpTopic, const wxString &docPath, wxHelpControllerBase *helpCtl, const bool init)
|
|
{
|
|
// Build the page name
|
|
wxString page;
|
|
int hashPos = helpTopic.Find('#');
|
|
if (hashPos < 0)
|
|
page = helpTopic + wxT(".html");
|
|
else
|
|
page = helpTopic.Left(hashPos) + wxT(".html") + helpTopic.Mid(hashPos);
|
|
|
|
// If the docPath ends in .pdf, then open the file in the browser. No
|
|
// bookmarks though :-(
|
|
if (docPath.Lower().EndsWith(wxT(".pdf")))
|
|
{
|
|
wxLaunchDefaultBrowser(docPath);
|
|
return;
|
|
}
|
|
|
|
// If the docPath doesn't end in .chm, .zip or .hhp, then we must be using
|
|
// plain HTML files, so just fire off the browser and be done with it.
|
|
if (!docPath.Lower().EndsWith(wxT(".hhp")) &&
|
|
#if defined (__WXMSW__) || wxUSE_LIBMSPACK
|
|
!docPath.Lower().EndsWith(wxT(".chm")) &&
|
|
#endif
|
|
!docPath.Lower().EndsWith(wxT(".zip")))
|
|
{
|
|
wxLaunchDefaultBrowser(docPath + page);
|
|
return;
|
|
}
|
|
|
|
// We must be using HTML Help, so init the appropriate help controller
|
|
// Note the path that we init for - if it changes, we need to init a
|
|
// new controller in case it's no longer the same type
|
|
if (init || !helpCtl)
|
|
{
|
|
// Get shot of the old help controller if there is one.
|
|
if (helpCtl)
|
|
delete helpCtl;
|
|
|
|
#ifdef __WXMSW__
|
|
// For Windows builds we us the MS HTML Help viewer for .chm files
|
|
if (docPath.Lower().EndsWith(wxT(".chm")) && wxFile::Exists(docPath))
|
|
{
|
|
helpCtl = new wxCHMHelpController();
|
|
helpCtl->Initialize(docPath);
|
|
}
|
|
else
|
|
#endif
|
|
#if wxUSE_LIBMSPACK
|
|
// If we can use a .chm file...
|
|
if ((docPath.Lower().EndsWith(wxT(".chm")) && wxFile::Exists(docPath)) ||
|
|
(docPath.Lower().EndsWith(wxT(".hhp")) && wxFile::Exists(docPath)) ||
|
|
(docPath.Lower().EndsWith(wxT(".zip")) && wxFile::Exists(docPath)))
|
|
#else
|
|
// Otherwise...
|
|
if ((docPath.Lower().EndsWith(wxT(".hhp")) && wxFile::Exists(docPath)) ||
|
|
(docPath.Lower().EndsWith(wxT(".zip")) && wxFile::Exists(docPath)))
|
|
#endif
|
|
{
|
|
helpCtl = new wxHtmlHelpController();
|
|
helpCtl->Initialize(docPath);
|
|
}
|
|
}
|
|
|
|
// Display the page using the help controller
|
|
// If it's foobar'ed, use the browser.
|
|
if (helpCtl)
|
|
{
|
|
if (helpTopic == wxT("index.html"))
|
|
helpCtl->DisplayContents();
|
|
else
|
|
helpCtl->DisplaySection(page);
|
|
}
|
|
else
|
|
{
|
|
wxLogError(_("The help source (\"%s\") could not be opened. Please check the help configuration options."), docPath.c_str());
|
|
}
|
|
}
|
|
|
|
wxString GetHtmlEntity(const wxChar ch)
|
|
{
|
|
// REWRITE THIS - IT'S (STILL) BLOODY INEFFICIENT!!
|
|
|
|
// Quick bailout
|
|
if ((ch >= 'a' && ch <= 'z') ||
|
|
(ch >= 'A' && ch <= 'Z') ||
|
|
(ch >= '0' && ch <= '9'))
|
|
return wxString(ch);
|
|
|
|
unsigned short ents[] =
|
|
{
|
|
34,
|
|
39,
|
|
38,
|
|
60,
|
|
62,
|
|
160,
|
|
161,
|
|
164,
|
|
162,
|
|
163,
|
|
165,
|
|
166,
|
|
167,
|
|
168,
|
|
169,
|
|
170,
|
|
171,
|
|
172,
|
|
173,
|
|
174,
|
|
8482,
|
|
175,
|
|
176,
|
|
177,
|
|
178,
|
|
179,
|
|
180,
|
|
181,
|
|
182,
|
|
183,
|
|
184,
|
|
185,
|
|
186,
|
|
187,
|
|
188,
|
|
189,
|
|
190,
|
|
191,
|
|
215,
|
|
247,
|
|
192,
|
|
193,
|
|
194,
|
|
195,
|
|
196,
|
|
197,
|
|
198,
|
|
199,
|
|
200,
|
|
201,
|
|
202,
|
|
203,
|
|
204,
|
|
205,
|
|
206,
|
|
207,
|
|
208,
|
|
209,
|
|
210,
|
|
211,
|
|
212,
|
|
213,
|
|
214,
|
|
216,
|
|
217,
|
|
218,
|
|
219,
|
|
220,
|
|
221,
|
|
222,
|
|
223,
|
|
224,
|
|
225,
|
|
226,
|
|
227,
|
|
228,
|
|
229,
|
|
230,
|
|
231,
|
|
232,
|
|
233,
|
|
234,
|
|
235,
|
|
236,
|
|
237,
|
|
238,
|
|
239,
|
|
240,
|
|
241,
|
|
242,
|
|
243,
|
|
244,
|
|
245,
|
|
246,
|
|
248,
|
|
249,
|
|
250,
|
|
251,
|
|
252,
|
|
253,
|
|
254,
|
|
255,
|
|
338,
|
|
339,
|
|
352,
|
|
353,
|
|
376,
|
|
710,
|
|
732,
|
|
8194,
|
|
8195,
|
|
8201,
|
|
8204,
|
|
8205,
|
|
8206,
|
|
8207,
|
|
8211,
|
|
8212,
|
|
8216,
|
|
8217,
|
|
8218,
|
|
8220,
|
|
8221,
|
|
8222,
|
|
8224,
|
|
8225,
|
|
8230,
|
|
8240,
|
|
8249,
|
|
8250,
|
|
8364,
|
|
0
|
|
};
|
|
|
|
int elem = 0;
|
|
|
|
while (ents[elem] != 0)
|
|
{
|
|
if (ch == ents[elem])
|
|
{
|
|
wxString ret = wxT("&#");
|
|
ret += NumToStr((long)ents[elem]);
|
|
ret += wxT(";");
|
|
return ret;
|
|
}
|
|
elem++;
|
|
}
|
|
|
|
return wxString(ch);
|
|
}
|
|
|
|
wxString HtmlEntities(const wxString &str)
|
|
{
|
|
wxString ret;
|
|
|
|
for (unsigned int x = 0; x < str.Length(); x++)
|
|
{
|
|
if (str[x] != 13)
|
|
ret += GetHtmlEntity(str[x]);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
#ifndef WIN32
|
|
wxString ExecProcess(const wxString &cmd)
|
|
{
|
|
wxString res;
|
|
FILE *f = popen(cmd.ToAscii(), "r");
|
|
|
|
if (f)
|
|
{
|
|
char buffer[1024];
|
|
int cnt;
|
|
while ((cnt = fread(buffer, 1, 1024, f)) > 0)
|
|
{
|
|
res += wxString::FromAscii(buffer);
|
|
}
|
|
pclose(f);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
int ExecProcess(const wxString &command, wxArrayString &result)
|
|
{
|
|
FILE *fp_command;
|
|
char buf[4098];
|
|
|
|
fp_command = popen(command.mb_str(wxConvUTF8), "r");
|
|
|
|
if (!fp_command)
|
|
return -1;
|
|
|
|
while(!feof(fp_command))
|
|
{
|
|
if (fgets(buf, 4096, fp_command) != NULL)
|
|
result.Add(wxString::FromAscii(buf));
|
|
}
|
|
|
|
return pclose(fp_command);
|
|
}
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
#if (_MSC_VER < 1300)
|
|
/* _ftol2 is more than VC7. */
|
|
extern "C" long _ftol( double );
|
|
extern "C" long _ftol2( double dblSource )
|
|
{
|
|
return _ftol( dblSource );
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
wxString firstLineOnly(const wxString &str)
|
|
{
|
|
wxString ip, tmp;
|
|
ip = str;
|
|
ip = ip.Trim(true).Trim(false);
|
|
|
|
if (ip.Contains(wxT("\r\n")) && (ip.First(wxT("\r")) < ip.First(wxT("\n"))))
|
|
{
|
|
tmp = ip.BeforeFirst('\r');
|
|
if (ip.BeforeFirst('\r').Length() != ip.Length())
|
|
tmp += wxT("...");
|
|
}
|
|
else
|
|
{
|
|
tmp = ip.BeforeFirst('\n');
|
|
if (ip.BeforeFirst('\n').Length() != ip.Length())
|
|
tmp += wxT("...");
|
|
}
|
|
|
|
return tmp;
|
|
}
|
|
|
|
bool pgAppMinimumVersion(const wxString &cmd, const int majorVer, const int minorVer)
|
|
{
|
|
wxArrayString output;
|
|
bool isEnterpriseDB = false;
|
|
|
|
#ifdef __WXMSW__
|
|
if (wxExecute(cmd + wxT(" --version"), output, 0) != 0)
|
|
#else
|
|
if (ExecProcess(cmd + wxT(" --version"), output) != 0)
|
|
#endif
|
|
{
|
|
wxLogError(_("Failed to execute: %s --version"), cmd.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Is this an EDB utility?
|
|
if (output[0].Contains(wxT("EnterpriseDB")))
|
|
isEnterpriseDB = true;
|
|
|
|
wxString version = output[0].AfterLast(' ');
|
|
int len=version.Length();
|
|
long actualMajor = 0, actualMinor = 0;
|
|
|
|
wxString tmp = wxT("");
|
|
int x = 0;
|
|
while(x<len&&(version[x] == '0' || version[x] == '1' || version[x] == '2' || version[x] == '3' || version[x] == '4' ||
|
|
version[x] == '5' || version[x] == '6' || version[x] == '7' || version[x] == '8' || version[x] == '9'))
|
|
{
|
|
tmp += version[x];
|
|
x++;
|
|
}
|
|
tmp.ToLong(&actualMajor);
|
|
x++;
|
|
|
|
tmp = wxT("");
|
|
while(x<len&&(version[x] == '0' || version[x] == '1' || version[x] == '2' || version[x] == '3' || version[x] == '4' ||
|
|
version[x] == '5' || version[x] == '6' || version[x] == '7' || version[x] == '8' || version[x] == '9'))
|
|
{
|
|
tmp += version[x];
|
|
x++;
|
|
}
|
|
|
|
tmp.ToLong(&actualMinor);
|
|
|
|
// EnterpriseDB's 8.3R1 utilties are based on PG8.2, so correct the version number here.
|
|
// This will need more work when 8.3R2 is released :-(
|
|
if (isEnterpriseDB && actualMajor == 8 && actualMinor == 3)
|
|
actualMinor = 2;
|
|
|
|
if (actualMajor > majorVer)
|
|
return true;
|
|
|
|
if (actualMajor == majorVer && actualMinor >= minorVer)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool isPgApp(const wxString &app)
|
|
{
|
|
if (!wxFile::Exists(app))
|
|
return false;
|
|
|
|
wxArrayString output;
|
|
|
|
#ifdef __WXMSW__
|
|
if (wxExecute(app + wxT(" --version"), output, 0) != 0)
|
|
#else
|
|
if (ExecProcess(app + wxT(" --version"), output) != 0)
|
|
#endif
|
|
{
|
|
wxLogError(_("Failed to execute: %s --version"), app.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (output[0].Contains(wxT("PostgreSQL")))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool isEdbApp(const wxString &app)
|
|
{
|
|
if (!wxFile::Exists(app))
|
|
return false;
|
|
|
|
wxArrayString output;
|
|
|
|
#ifdef __WXMSW__
|
|
if (wxExecute(app + wxT(" --version"), output, 0) != 0)
|
|
#else
|
|
if (ExecProcess(app + wxT(" --version"), output) != 0)
|
|
#endif
|
|
{
|
|
wxLogError(_("Failed to execute: %s --version"), app.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (output[0].Contains(wxT("EnterpriseDB")))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool isGpApp(const wxString &app)
|
|
{
|
|
if (!wxFile::Exists(app))
|
|
return false;
|
|
|
|
wxArrayString output;
|
|
|
|
#ifdef __WXMSW__
|
|
if (wxExecute(app + wxT(" --version"), output, 0) != 0)
|
|
#else
|
|
if (ExecProcess(app + wxT(" --version"), output) != 0)
|
|
#endif
|
|
{
|
|
wxLogError(_("Failed to execute: %s --version"), app.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (output[0].Contains(wxT("8.2"))) // Ugly... No way to tell Greenplum app from PostgreSQL 8.2 app
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
wxString sanitizePath(const wxString &path)
|
|
{
|
|
if (path.Length())
|
|
{
|
|
wxFileName fn = path;
|
|
fn.Normalize();
|
|
return fn.GetLongPath();
|
|
}
|
|
|
|
return wxEmptyString;
|
|
}
|
|
|
|
/**
|
|
* FUNCTION: commandLineCleanOption
|
|
* INPUTS:
|
|
* option - input string needs to be reformatted
|
|
* schemaObject - Is this an object related to schema?
|
|
* PURPOSE:
|
|
* - Fixup a (double-quoted) string for use on the command line
|
|
*/
|
|
wxString commandLineCleanOption(const wxString &option, bool schemaObject)
|
|
{
|
|
wxString tmp = option;
|
|
|
|
if (schemaObject)
|
|
{
|
|
// Replace double-quote with slash & double-quote
|
|
tmp.Replace(wxT("\""), wxT("\\\""));
|
|
}
|
|
else
|
|
{
|
|
// If required, clean the string to know the real object name
|
|
if (option.StartsWith(wxT("\"")) && option.EndsWith(wxT("\"")))
|
|
tmp = option.AfterFirst((wxChar)'"').BeforeLast((wxChar)'"');
|
|
|
|
// Replace single splash to double-splash
|
|
tmp.Replace(wxT("\\"), wxT("\\\\"));
|
|
|
|
// Replace double-quote with slash & double-quote
|
|
tmp.Replace(wxT("\""), wxT("\\\""));
|
|
|
|
// Replace double (slash & double-quote) combination to single (slash & double-quote) combination
|
|
tmp.Replace(wxT("\\\"\\\""), wxT("\\\""));
|
|
|
|
// Add the double quotes
|
|
tmp = wxT("\"") + tmp + wxT("\"");
|
|
}
|
|
|
|
return tmp;
|
|
}
|
|
|
|
#endif // PGSCLI
|
|
|
|
// Get an array from a comma separated list
|
|
bool getArrayFromCommaSeparatedList(const wxString &str, wxArrayString &res)
|
|
{
|
|
size_t len = str.Len(), index = 0, nBracketLevel = 0, startArray = 0;
|
|
bool inSingleQuote = false, inDoubleQuote = false;
|
|
|
|
if (len == 0)
|
|
return true;
|
|
|
|
for(; index < len; index++)
|
|
{
|
|
wxChar curr = str.GetChar(index);
|
|
if (!inDoubleQuote && curr == (wxChar)'\'')
|
|
inSingleQuote = !inSingleQuote;
|
|
else if (!inSingleQuote && curr == (wxChar)'"')
|
|
inDoubleQuote = !inDoubleQuote;
|
|
else if (!inDoubleQuote && !inSingleQuote && curr == (wxChar)'(')
|
|
nBracketLevel++;
|
|
else if (!inDoubleQuote && !inSingleQuote && curr == (wxChar)')')
|
|
nBracketLevel--;
|
|
else if (!inDoubleQuote && !inSingleQuote && nBracketLevel == 0 && curr == (wxChar)',')
|
|
{
|
|
if (index != startArray)
|
|
res.Add(str.SubString(startArray, index - 1).Trim(true).Trim(false));
|
|
else
|
|
res.Add(wxEmptyString);
|
|
startArray = index + 1;
|
|
}
|
|
}
|
|
if (inDoubleQuote || inSingleQuote || nBracketLevel != 0)
|
|
return false;
|
|
|
|
// Add last value to array
|
|
res.Add(str.SubString(startArray, index).Trim(true).Trim(false));
|
|
|
|
return true;
|
|
}
|
|
|