From 4f10ebc4772b720a56cde8a869a020b8d63a4c3e Mon Sep 17 00:00:00 2001 From: lsv Date: Thu, 24 Nov 2022 19:19:37 +0500 Subject: [PATCH] Support GitLab experimental MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлена возможность сохранять SQL представления объектов в GitLab Описание в Readme.MD --- README.md | 43 + ctl/ctlGitPanel.cpp | 1418 ++++++++++++ frm/frmMain.cpp | 12 + include/ctl/ctlGitPanel.h | 111 + include/frm/frmMain.h | 2 + include/precomp.h | 3 + include/utils/json/json_defs.h | 209 ++ include/utils/json/jsonreader.h | 148 ++ include/utils/json/jsonval.h | 440 ++++ include/utils/json/jsonwriter.h | 121 ++ pgAdmin3.vcxproj | 10 + pgAdmin3.vcxproj.filters | 30 + utils/json/jsonreader.cpp | 2131 ++++++++++++++++++ utils/json/jsonval.cpp | 3555 +++++++++++++++++++++++++++++++ utils/json/jsonwriter.cpp | 1276 +++++++++++ 15 files changed, 9509 insertions(+) create mode 100644 ctl/ctlGitPanel.cpp create mode 100644 include/ctl/ctlGitPanel.h create mode 100644 include/utils/json/json_defs.h create mode 100644 include/utils/json/jsonreader.h create mode 100644 include/utils/json/jsonval.h create mode 100644 include/utils/json/jsonwriter.h create mode 100644 utils/json/jsonreader.cpp create mode 100644 utils/json/jsonval.cpp create mode 100644 utils/json/jsonwriter.cpp diff --git a/README.md b/README.md index f1a6b8b..d1d9564 100644 --- a/README.md +++ b/README.md @@ -220,3 +220,46 @@ English version issue #18 - поддержка списка колонок при задании FK - поддержка NULLS NOT DISTINCT для уникальных индексов +24.11.2022 + Добавлена эксперементальная функция работы с gitLab. + Для работы с gitlab необходимо положить файл gitlab.json в каталог %APPDATA%\postgresql. + Вот пример содержимого файла: +{ +"url": "https://gl.mympany.ru:4443/api/v4/", +"private_token": "V3JYpw2x5rr61yGe_M2e", +"project_id": "532" +} + После запуска pgAdmin3 появится вкладка Git на которой будут дополнительные закладки. + Пока для работы с GitLab можно выполнять только коммиты на дополнительной вкладке Commit. + В GitLab сохраняются только содержимое объектов схем. Сохраняется только SQL представления. +Алгоритм работы такой: + - из файл gitlab.json берётся информация для соединения с gitLab + - из ветки по умолчанию (обычно это main) считывается файл pgadmin3.json с общими настройки. + - если такого файла не то настройки беруться из gitlab.json + - вот пример настроек из pgadmin3.json +{ +"ignore_schema": ["public","repack","schedule"], +"control_objects": ["Functions","Views","Tables","Trigger Functions","Procedures","Schemas","Schema","Database"], +"maps_branch_to_dbname":[ + {"branch": "asu", + "list_db": ["asu"] + } + , + {"branch": "common_db", + "list_db": ["dbname1","dbname2"] + } + +] +} +Где: + "ignore_schema" - список схем которые не нужно сохранять и git + "control_objects" - перечень типов объектов схемы которые нужно сохранять. + "maps_branch_to_dbname" - сопостовление имен веток и имен БД. + - нажимается кнопка "Load Git" и загружается SQL предствления объектов из GitLab + После этой операции список "List commit files" будет заполнен расхождениями текущей БД и веткой в GitLab + - Если выбрать несколько элементов (или все нажав Ctrl+A) то указав название коммита и нажав на + кнопку "commit" можно закомитить текущее SQL представление выбранных элементов в GitLab + - нажатие правой кнопки мыши на каком либо элементе списка покажет различия между объектов в БД и GitLab. +Все прочие кнопки и закладки использовать не нужно. +Типовой способ использования, ведение историй изменений объектов БД в GitLab. + diff --git a/ctl/ctlGitPanel.cpp b/ctl/ctlGitPanel.cpp new file mode 100644 index 0000000..36401c7 --- /dev/null +++ b/ctl/ctlGitPanel.cpp @@ -0,0 +1,1418 @@ + +// +#include "pgAdmin3.h" + +#include "frm/frmMain.h" +#include "ctl/ctlGitPanel.h" +#include "ctl/ctlSQLBox.h" +#include "wx/notebook.h" +#include "wx/artprov.h" +#include "wx/creddlg.h" +#include "wx/webrequest.h" +#include "wx/filedlg.h" +#include "wx/image.h" +#include "schema/pgObject.h" +#include "schema/pgServer.h" +#include "utils/json/jsonval.h" +#include "utils/json/jsonwriter.h" +#include "utils/json/jsonreader.h" +#include +#include +#include +#include "images/gqbAdd.pngc" +#include "images/gqbRemove.pngc" +#include "images/conversion.pngc" +#include "../utils/diff_match_patch.h" + +enum +{ + MARGIN_LINE_NUMBERS +}; + + +class SourceViewDialog : public wxFrame +{ + ctlSQLBox* m_text1; + ctlSQLBox* m_text2; + bool m_changing_values; + wxCheckBox* m_visibleSpace; + wxCheckBox* m_showNumber; + +public: + + void onClose(wxCloseEvent& evt) + { + //EndModal(GetReturnCode()); + evt.Skip(); + + } + void showNumber(ctlSQLBox* text, bool visible) + { + if (visible) { + long int width = text->TextWidth(wxSTC_STYLE_LINENUMBER, + wxT(" ") + NumToStr((long int)text->GetLineCount()) + wxT(" ")); + if (width != text->GetMarginWidth(0)) + { + text->SetMarginWidth(0, width); + text->Update(); + } + } + else { + text->SetMarginWidth(0, 0); + text->Update(); + } + } + ctlSQLBox* createSTC() + { + ctlSQLBox* text = new ctlSQLBox(this, wxID_ANY); + +// text->SetMarginWidth(MARGIN_LINE_NUMBERS, 20); +// text->StyleSetForeground(wxSTC_STYLE_LINENUMBER, wxColour(75, 75, 75)); +// text->StyleSetBackground(wxSTC_STYLE_LINENUMBER, wxColour(220, 220, 220)); +// text->SetMarginType(MARGIN_LINE_NUMBERS, wxSTC_MARGIN_NUMBER); + text->SetEOLMode(2); + + showNumber(text,true); + + //text->StyleSetForeground(wxSTC_H_DOUBLESTRING, wxColour(255, 0, 0)); + //text->StyleSetForeground(wxSTC_H_SINGLESTRING, wxColour(255, 0, 0)); + //text->StyleSetForeground(wxSTC_H_ENTITY, wxColour(255, 0, 0)); + //text->StyleSetForeground(wxSTC_H_TAG, wxColour(0, 150, 0)); + //text->StyleSetForeground(wxSTC_H_TAGUNKNOWN, wxColour(0, 150, 0)); + //text->StyleSetForeground(wxSTC_H_ATTRIBUTE, wxColour(0, 0, 150)); + //text->StyleSetForeground(wxSTC_H_ATTRIBUTEUNKNOWN, wxColour(0, 0, 150)); + //text->StyleSetForeground(wxSTC_H_COMMENT, wxColour(150, 150, 150)); + + return text; + } + void addIndicText(ctlSQLBox* ctl,std::wstring tex,int indic) { + wxString t(tex); + + ctl->AddText(t); + if (indic > 0) { + ctl->IndicatorFillRange(ctl->GetLength()-t.Len(), t.Len()); + } + }; + void difftext(ctlSQLBox* ctlL, ctlSQLBox* ctlR, wxString sL,wxString sR) { + //Diff_EditCost = 4; + //Match_Threshold = 0.5; + //Match_Distance = 1000; + diff_match_patch dmp(4, 0.5, 1000); + std::list diffs; + if (sL == sR) { + return ; + } + diffs = dmp.diff_main(sL.wc_str(), sR.wc_str(), true); + int nstart = 0; + int pos = 0; + std::wstring cur_l; + std::wstring ncur_l;// std::wstring p_ncur_l; std::wstring p_ncur_r; + std::wstring cur_r; + std::wstring ncur_r; + std::wstring tex; + std::wstring t; + std::wstring tableline; + int rline = 1, lline = 1; + std::list::const_iterator it; // + it = diffs.begin(); // + Diff aDiff; + bool modify = false; + nstart = 0; + int s_indicHighlight=20; + ctlL->IndicatorSetForeground(s_indicHighlight, wxColour(246, 185, 100)); + ctlL->IndicatorSetAlpha(s_indicHighlight, 50); + ctlL->IndicatorSetStyle(s_indicHighlight, wxSTC_INDIC_ROUNDBOX); + ctlL->SetIndicatorCurrent(s_indicHighlight); + + ctlR->IndicatorSetForeground(s_indicHighlight, wxColour(246, 185, 100)); + ctlR->IndicatorSetAlpha(s_indicHighlight, 50); + ctlR->IndicatorSetStyle(s_indicHighlight, wxSTC_INDIC_ROUNDBOX); + ctlR->SetIndicatorCurrent(s_indicHighlight); + + while (it != diffs.end()) // + { + aDiff = *it; + tex = aDiff.text; + nstart = 0; + while (nstart < tex.length()) { + pos = tex.find('\n', nstart); + if (pos == -1) { t.assign(tex, nstart, tex.length()); nstart = tex.length(); } + else { t.assign(tex, nstart, pos - nstart); nstart = pos; } + if (t.length() > 0) { + // + if (aDiff.operation == Operation::INSERT) { + cur_r += L"" + t + L""; addIndicText(ctlR, t, s_indicHighlight); + } + if (aDiff.operation == 0) { + cur_l += L"" + t + L""; addIndicText(ctlL,t, s_indicHighlight); + } + if (aDiff.operation == Operation::EQUAL) { + cur_r += t; + cur_l += t; + addIndicText(ctlL, t, 0); + addIndicText(ctlR, t, 0); + } + else modify = true; + // + } + else + { + // \n + nstart = pos + 1; + ncur_l = std::to_wstring(lline); + ncur_l = L""; ncur_r = L""; + // if (p_ncur_r==ncur_r) { ncur_r=L""; modify=true;} + // if (p_ncur_l==ncur_l) { ncur_l=L""; modify=true;} + + std::wstring t_cur_l = cur_l; + std::wstring t_cur_r = cur_r; + if (aDiff.operation == 0) { + t_cur_r = L""; + addIndicText(ctlL, L"\n", s_indicHighlight); + ncur_l = std::to_wstring(lline); + modify = true; + lline++; + } + else if (aDiff.operation == Operation::INSERT) { + t_cur_l = L""; + addIndicText(ctlR, L"\n", s_indicHighlight); + ncur_r = std::to_wstring(rline); + modify = true; + rline++; + } + else if (aDiff.operation == Operation::EQUAL) { + ncur_r = std::to_wstring(rline); + ncur_l = std::to_wstring(lline); + rline++; lline++; + addIndicText(ctlL, L"\n", 0); + addIndicText(ctlR, L"\n", 0); + } + // if (modify) countdiffline++; + // if (( (ncur_r.empty()&&(!ncur_l.empty())) + // ||(ncur_l.empty()&&(!ncur_r.empty())) + // )&&(!modify)) modify=true; + // create columns + //left + tableline += L""; + tableline += modify ? L"" + ncur_l + "" : L"" + ncur_l + ""; + tableline += L"
" + t_cur_l + "
"; + // right + tableline += L""; + tableline += modify ? L"" + ncur_r + "" : L"" + ncur_r + ""; + tableline += L"
" + t_cur_r + "
"; + tableline += L""; + + if (aDiff.operation == 0) { + cur_l = L""; + } + else if (aDiff.operation == Operation::INSERT) { + cur_r = L""; + } + else if (aDiff.operation == Operation::EQUAL) { + cur_r = L""; cur_l = L""; + } + + // + modify = false; + } + } // Diff + ++it; + } + + } + SourceViewDialog(wxWindow* parent, wxString sqlL, wxString sqlR) : + wxFrame(parent, wxID_ANY, L"Compare git to DB", wxDefaultPosition, wxSize(900, 600)) + { + m_changing_values = false; + m_text1 = createSTC(); + //m_text1->SetText(sqlL); + + m_text2 = createSTC(); + //m_text2->SetText(sqlR); + + difftext(m_text1, m_text2, sqlL, sqlR); + + + + wxBoxSizer* bSizer1; + bSizer1 = new wxBoxSizer(wxVERTICAL); + + wxPanel* m_panelSql = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + wxBoxSizer* bSizer2; + bSizer2 = new wxBoxSizer(wxHORIZONTAL); + + bSizer2->Add(m_text1, 1, wxEXPAND, 5); + + bSizer2->Add(m_text2, 1, wxEXPAND, 5); + + + m_panelSql->SetSizer(bSizer2); + m_panelSql->Layout(); + bSizer2->Fit(m_panelSql); + bSizer1->Add(m_panelSql, 1, wxEXPAND, 2); + + wxPanel* m_panelOpt = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + wxBoxSizer* bSizer4; + bSizer4 = new wxBoxSizer(wxHORIZONTAL); + + m_visibleSpace = new wxCheckBox(m_panelOpt, wxID_ANY, wxT("Show space simbol"), wxDefaultPosition, wxDefaultSize, 0); + bSizer4->Add(m_visibleSpace, 0, wxALL, 5); + + m_showNumber = new wxCheckBox(m_panelOpt, wxID_ANY, wxT("Show number line"), wxDefaultPosition, wxDefaultSize, 0); + bSizer4->Add(m_showNumber, 0, wxALL, 5); + + + bSizer4->Add(0, 0, 1, wxEXPAND, 5); + + wxButton* m_btn_close = new wxButton(m_panelOpt, wxID_ANY, wxT("Close"), wxDefaultPosition, wxDefaultSize, 0); + bSizer4->Add(m_btn_close, 0, wxALL, 5); + +// wxButton *m_btn_cancel = new wxButton(m_panelOpt, wxID_ANY, wxT("Cancel"), wxDefaultPosition, wxDefaultSize, 0); +// bSizer4->Add(m_btn_cancel, 0, wxALL, 5); + + + m_panelOpt->SetSizer(bSizer4); + m_panelOpt->Layout(); + bSizer4->Fit(m_panelOpt); + bSizer1->Add(m_panelOpt, 0, wxALL | wxEXPAND, 5); + + + this->SetSizer(bSizer1); + this->Layout(); + + this->Centre(wxBOTH); + + m_visibleSpace->Bind(wxEVT_CHECKBOX, &SourceViewDialog::OnShowCheckBoxSpace, this); + m_showNumber->Bind(wxEVT_CHECKBOX, &SourceViewDialog::OnShowCheckShowNumber, this); + m_btn_close->Bind(wxEVT_BUTTON, &SourceViewDialog::onClose2, this); + //Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(SourceViewDialog::onClose), NULL, this); + //m_text1->Connect(wxEVT_STC_PAINTED, wxStyledTextEventHandler(SourceViewDialog::onScrollLeft), NULL, this); + //m_text2->Connect(wxEVT_STC_PAINTED, wxStyledTextEventHandler(SourceViewDialog::onScrollRight), NULL, this); + Bind(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(SourceViewDialog::onClose), this); + m_text1->Bind(wxEVT_STC_PAINTED, wxStyledTextEventHandler(SourceViewDialog::onScrollLeft), this); + m_text2->Bind(wxEVT_STC_PAINTED, wxStyledTextEventHandler(SourceViewDialog::onScrollRight), this); + + } + void onClose2(wxCommandEvent& evt) { + //onClose(wxCloseEvent(NULL)); + Close(); + } + void OnShowCheckBoxSpace(wxCommandEvent& evt) { + bool bVal= m_visibleSpace->IsChecked(); + m_text1->SetViewWhiteSpace(bVal ? wxSTC_WS_VISIBLEALWAYS : wxSTC_WS_INVISIBLE); + m_text1->SetViewEOL(bVal ? 1: 0); + m_text2->SetViewWhiteSpace(bVal ? wxSTC_WS_VISIBLEALWAYS : wxSTC_WS_INVISIBLE); + m_text2->SetViewEOL(bVal ? 1 : 0); + + } + void OnShowCheckShowNumber(wxCommandEvent& evt) { + bool bVal = m_showNumber->IsChecked(); + showNumber(m_text1, bVal); + showNumber(m_text2, bVal); + } + + void onScrollLeft(wxStyledTextEvent& evt) + { + if (m_changing_values) return; + m_changing_values = true; + + int fvl = m_text1->GetFirstVisibleLine(); + if (m_text2->GetFirstVisibleLine() != fvl) + { + m_text2->ScrollToLine(fvl); + // ShowLines + } + + m_changing_values = false; + } + + void onScrollRight(wxStyledTextEvent& evt) + { + if (m_changing_values) return; + m_changing_values = true; + + int fvl = m_text2->GetFirstVisibleLine(); + if (m_text1->GetFirstVisibleLine() != fvl) + { + m_text1->ScrollToLine(fvl); + // ShowLines + } + + m_changing_values = false; + } +}; + +ctlGitPanel::ctlGitPanel(wxWindow* parent, frmMain* form, wxJSONValue cf) : + wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) +{ + formMain = form; + cfg = cf; + + //, wxPoint(0, 0), wxDefaultSize, wxSUNKEN_BORDER | wxCLIP_CHILDREN + +// Bind(wxEVT_CLOSE_WINDOW, &WebRequestFrame::OnClose, this); + + // Prepare UI controls + wxSizer* mainSizer = new wxBoxSizer(wxVERTICAL); + + mainSizer->Add(new wxStaticText(this, wxID_ANY, "Request URL:"), + wxSizerFlags().Border()); + m_urlTextCtrl = new wxTextCtrl(this, wxID_ANY, + "https://www.wxwidgets.org/downloads/logos/blocks.png", + wxDefaultPosition, wxDefaultSize, + wxTE_PROCESS_ENTER); + mainSizer->Add(m_urlTextCtrl, + wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT)); + //m_urlTextCtrl->Bind(wxEVT_TEXT_ENTER, &ctlGitPanel::OnLoadGitButton, this); + + m_notebook = new wxNotebook(this, wxID_ANY); + m_notebook->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &ctlGitPanel::OnNotebookPageChanged, this); + + // Image page + //wxPanel* imagePanel = new wxPanel(m_notebook); + //wxSizer* imageSizer = new wxBoxSizer(wxVERTICAL); + + //m_imageStaticBitmap = new wxStaticBitmap(imagePanel, + // wxID_ANY, wxArtProvider::GetBitmap(wxART_MISSING_IMAGE)); + //imageSizer->Add(m_imageStaticBitmap, wxSizerFlags(1).Expand()); + + //imagePanel->SetSizer(imageSizer); + //m_notebook->AddPage(imagePanel, "Image", true); + + // Text page + wxPanel* textPanel = new wxPanel(m_notebook); + wxSizer* textSizer = new wxBoxSizer(wxVERTICAL); + + m_postCheckBox = new wxCheckBox(textPanel, wxID_ANY, "Post request body"); + textSizer->Add(m_postCheckBox, wxSizerFlags().Border()); + m_postCheckBox->Bind(wxEVT_CHECKBOX, &ctlGitPanel::OnPostCheckBox, this); + + m_postRequestTextCtrl = new wxTextCtrl(textPanel, wxID_ANY, + "app=WebRequestSample&version=1", + wxDefaultPosition, wxSize(-1, FromDIP(60)), wxTE_MULTILINE); + textSizer->Add(m_postRequestTextCtrl, + wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT)); + + textSizer->Add(new wxStaticText(textPanel, wxID_ANY, "Request body content type:"), + wxSizerFlags().Border()); + m_postContentTypeTextCtrl = new wxTextCtrl(textPanel, wxID_ANY, + "application/x-www-form-urlencoded"); + textSizer->Add(m_postContentTypeTextCtrl, + wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT)); + + textSizer->Add(new wxStaticText(textPanel, wxID_ANY, "Response body:"), + wxSizerFlags().Border()); + m_textResponseTextCtrl = new wxTextCtrl(textPanel, wxID_ANY, "", + wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY); + m_textResponseTextCtrl->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); + textSizer->Add(m_textResponseTextCtrl, + wxSizerFlags(1).Expand().Border(wxLEFT | wxRIGHT | wxBOTTOM)); + + textPanel->SetSizer(textSizer); + m_notebook->AddPage(textPanel, "Debug"); + + // Download page + //wxPanel* downloadPanel = new wxPanel(m_notebook); + //wxSizer* downloadSizer = new wxBoxSizer(wxVERTICAL); + //wxStaticText* downloadHeader = new wxStaticText(downloadPanel, wxID_ANY, + // "The URL will be downloaded to a file.\n" + // "Progress will be shown and you will be asked, where\n" + // "to save the file when the download completed."); + //downloadSizer->Add(downloadHeader, wxSizerFlags().Expand().Border()); + //downloadSizer->AddStretchSpacer(); + //m_downloadGauge = new wxGauge(downloadPanel, wxID_ANY, 100); + //downloadSizer->Add(m_downloadGauge, wxSizerFlags().Expand().Border()); + //m_downloadStaticText = new wxStaticText(downloadPanel, wxID_ANY, "-1-"); + //downloadSizer->Add(m_downloadStaticText, wxSizerFlags().Expand().Border()); + + //downloadSizer->AddStretchSpacer(); + + //downloadPanel->SetSizer(downloadSizer); + //m_notebook->AddPage(downloadPanel, "Download"); + + // Advanced page + //wxPanel* advancedPanel = new wxPanel(m_notebook); + //wxSizer* advSizer = new wxBoxSizer(wxVERTICAL); + //wxStaticText* advHeader = new wxStaticText(advancedPanel, wxID_ANY, + // "As an example of processing data while\n" + // "it's being received from the server, every\n" + // "zero byte in the response will be counted below."); + //advSizer->Add(advHeader, wxSizerFlags().Expand().Border()); + + //advSizer->AddStretchSpacer(); + //m_advCountStaticText = new wxStaticText(advancedPanel, wxID_ANY, "0", + // wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE_HORIZONTAL | wxST_NO_AUTORESIZE); + //m_advCountStaticText->SetFont(m_advCountStaticText->GetFont() + // .MakeBold().MakeLarger().MakeLarger()); + //advSizer->Add(m_advCountStaticText, wxSizerFlags().Expand().Border()); + //advSizer->AddStretchSpacer(); + + //advancedPanel->SetSizer(advSizer); + + //m_notebook->AddPage(advancedPanel, "Advanced"); +// branch + wxPanel* branchPanel = new wxPanel(m_notebook); + wxSizer* brnSizer = new wxBoxSizer(wxVERTICAL); + brnSizer->Add(new wxStaticText(branchPanel, wxID_ANY, "List branch:"), + wxSizerFlags().Border()); +// brnSizer->Add(brnHeader, wxSizerFlags().Expand().Border()); + + //brnSizer->AddStretchSpacer(); + m_Branch_List_Ctrl = new wxComboBox(branchPanel, wxID_ANY, + ""); + brnSizer->Add(m_Branch_List_Ctrl, wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT)); + + m_branchListButton = new wxButton(branchPanel, wxID_ANY, "Branch List"); + m_branchListButton->Bind(wxEVT_BUTTON, &ctlGitPanel::OnBranchListButton, this); + //m_cancelButton->Disable(); + brnSizer->Add(m_branchListButton, wxSizerFlags().Border()); + + m_branchDeleteButton = new wxButton(branchPanel, wxID_ANY, "Branch Delete"); + m_branchDeleteButton->Bind(wxEVT_BUTTON, &ctlGitPanel::OnBranchDeleteButton, this); + brnSizer->Add(m_branchDeleteButton, wxSizerFlags().Border()); + branchPanel->SetSizer(brnSizer); + m_notebook->AddPage(branchPanel, "Branch"); + +// commits + wxPanel* commitPanel = new wxPanel(m_notebook); + wxSizer* cmtSizer = new wxBoxSizer(wxVERTICAL); + cmtSizer->Add(new wxStaticText(commitPanel, wxID_ANY, "List commit files:"), + wxSizerFlags().Border()); + // brnSizer->Add(brnHeader, wxSizerFlags().Expand().Border()); + + //brnSizer->AddStretchSpacer(); + + m_commit_List_View = new wxListView(commitPanel, wxID_ANY, wxDefaultPosition, wxSize(-1,120), wxLC_LIST| wxSIMPLE_BORDER| wxLC_NO_HEADER); + wxImageList *imaget = new wxImageList(19, 16,false,3); + imaget->Add(*gqbAdd_png_img); + imaget->Add(*gqbRemove_png_img); + //imaget->Add(*conversion_png_img); + wxBitmap image1 = wxBitmap(conversion_png_img->Scale(19, 16)); + imaget->Add(image1); + m_commit_List_View->SetImageList(imaget, wxIMAGE_LIST_SMALL); + m_commit_List_View->Bind(wxEVT_KEY_UP, &ctlGitPanel::OnKEY_UP, this); + m_commit_List_View->Bind(wxEVT_LIST_ITEM_RIGHT_CLICK, &ctlGitPanel::OnListRClick, this); + + cmtSizer->Add(m_commit_List_View, wxSizerFlags().Expand().Border(wxLEFT | wxRIGHT)); + + cmtSizer->Add(new wxStaticText(commitPanel, wxID_ANY, "Message commit:"), + wxSizerFlags().Border()); + m_CommentTextCtrl = new wxTextCtrl(commitPanel, wxID_ANY, "", + wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE); + //m_CommentTextCtrl->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); + cmtSizer->Add(m_CommentTextCtrl, + wxSizerFlags(1).Expand().Border(wxLEFT | wxRIGHT | wxBOTTOM)); + + m_commitButton = new wxButton(commitPanel, wxID_ANY, "commit"); + m_commitButton->Bind(wxEVT_BUTTON, &ctlGitPanel::OnCommitButton, this); + //m_cancelButton->Disable(); + cmtSizer->Add(m_commitButton, wxSizerFlags().Border()); + +// m_commitDeleteButton = new wxButton(commitPanel, wxID_ANY, "commit Delete"); +// m_commitDeleteButton->Bind(wxEVT_BUTTON, &ctlGitPanel::OncommitDeleteButton, this); + // cmtSizer->Add(m_commitDeleteButton, wxSizerFlags().Border()); + + + commitPanel->SetSizer(cmtSizer); + m_notebook->AddPage(commitPanel, "Commit"); + +/////////////////////////// + + mainSizer->Add(m_notebook, wxSizerFlags(1).Expand().Border()); + + wxStdDialogButtonSizer* btn2Sizer = new wxStdDialogButtonSizer(); + m_cancelButton = new wxButton(this, wxID_CANCEL, "Cancel"); + m_cancelButton->Bind(wxEVT_BUTTON, &ctlGitPanel::OnCancelButton, this); + m_cancelButton->Disable(); + btn2Sizer->AddButton(m_cancelButton); + + m_startButton = new wxButton(this, wxID_OK, "&Load Git"); + m_startButton->Bind(wxEVT_BUTTON, &ctlGitPanel::OnLoadGitButton, this); + btn2Sizer->AddButton(m_startButton); + m_link= new wxHyperlinkCtrl(this, wxID_ANY, "","",wxDefaultPosition,wxSize(-1,-1)); + // btn2Sizer->AddStretchSpacer(); + btn2Sizer->Add(m_link, wxSizerFlags(1).Expand().Border(wxLEFT ).Top()); + + + btn2Sizer->Realize(); + mainSizer->Add(btn2Sizer, wxSizerFlags().Expand().Border()); + + wxCommandEvent evt; + OnPostCheckBox(evt); + + //SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); + SetSizer(mainSizer); + + SetSize(FromDIP(wxSize(540, 500))); + + wxLogStatus(this, "%s", wxWebSession::GetDefault().GetLibraryVersionInfo().ToString()); + + m_downloadProgressTimer.Bind(wxEVT_TIMER, + &ctlGitPanel::OnProgressTimer, this); + + GetBranchList(true); +// load pgadmin3.json + wxString jsonText = GetRepositoryFile("main", "pgadmin3.json"); + if (!jsonText.IsEmpty()) { + wxJSONReader reader; + wxJSONValue c; + int errnum = reader.Parse(jsonText, &c); + if (errnum > 0) { + wxLogError("Branch 'main'. Parse json file pgadmin3.json errors. Number errors %d", errnum); + } + else + { + if ((!c["ignore_schema"].IsNull()) && cfg["ignore_schema"].IsNull()) cfg["ignore_schema"] = c["ignore_schema"]; + if ((!c["control_objects"].IsNull()) && cfg["control_objects"].IsNull()) cfg["control_objects"] = c["control_objects"]; + if ((!c["maps_branch_to_dbname"].IsNull()) && cfg["maps_branch_to_dbname"].IsNull()) cfg["maps_branch_to_dbname"] = c["maps_branch_to_dbname"]; + } + + } + +} +void ctlGitPanel::OnListRClick(wxListEvent& evt) { + int i = evt.GetIndex(); + wxListItem it=evt.GetItem(); + wxString pat = it.GetText(); + if (it) { + } + wxString sql_git = m_git_content[pat]; + wxString sql_db = m_base_content[pat]; + SourceViewDialog* dlg = new SourceViewDialog(NULL, sql_git,sql_db); + dlg->Show(); + +} +void ctlGitPanel::OnKEY_UP(wxKeyEvent& event) { + bool ctrl = false; + if ((event.GetModifiers() & wxMOD_CONTROL) == wxMOD_CONTROL) { + ctrl = true; + } + if (event.GetKeyCode() == WXK_DELETE && ctrl) { + wxArrayInt num; + for (int i = 0; i < m_commit_List_View->GetItemCount(); i++) { + if (!m_commit_List_View->IsSelected(i)) continue; + num.Add(i); + } + for (int j = num.Count() - 1; j >= 0; j--) { + m_commit_List_View->DeleteItem(num[j]); + } + event.Skip(true); + } + if (event.GetKeyCode() == 'A' && ctrl) { + for (int i = 0; i < m_commit_List_View->GetItemCount(); i++) { + m_commit_List_View->Select(i); + } + event.Skip(true); + } + + +} +ctlGitPanel::~ctlGitPanel() +{ + // We have to block until the web request completes, but we need to + // process events while doing it. + //Hide(); + if (m_currentRequest.IsOk()) + m_currentRequest.Cancel(); + + while (m_currentRequest.IsOk()) + { + wxYield(); + } +} +wxJSONValue ctlGitPanel::GetConfig() { + wxString c; + wxJSONValue cfg; + wxString p = wxStandardPaths::Get().GetUserConfigDir() + wxFileName::GetPathSeparator() + "postgresql"+ wxFileName::GetPathSeparator() +"gitlab.json"; + if (!wxFileExists(p)) return cfg; + wxFileInputStream input(p); + if (input.IsOk()) { + wxJSONReader reader; + int errnum=reader.Parse(input, &cfg); + if (errnum > 0) { + wxLogError("Parse json file %s errors. Number errors %d",p,errnum); + } + else { + + } + + } + return cfg; +} +wxJSONValue ctlGitPanel::execRequest(wxString url, wxJSONValue args,wxString cmd) { + wxWebRequest request = wxWebSession::GetDefault().CreateRequest( + this, + url + ); + wxJSONValue r; + if (!request.IsOk()) { + // This is not expected, but handle the error somehow. + return r; + } + wxString pt; + cfg["private_token"].AsString(pt); + request.SetHeader("PRIVATE-TOKEN", pt); + if (cmd != "GET") request.SetMethod(cmd); + if (!args.IsNull()) { + wxJSONWriter writer(wxJSONWRITER_NONE); + wxString str; + writer.Write(args, str); +// str = "{}"; + request.SetData(str, "application/json"); + m_postContentTypeTextCtrl->SetValue(str); + } + wxString rez; + wxString answer; + wxString link; + response_link = ""; + // Bind state event + Bind(wxEVT_WEBREQUEST_STATE, [&link, &rez, &answer](wxWebRequestEvent& evt) { + switch (evt.GetState()) + { + // Request completed + case wxWebRequest::State_Completed: + { + answer = evt.GetResponse().AsString(); + rez.Printf("Loaded %lld bytes text data (Status: %d %s)", + evt.GetResponse().GetContentLength(), + evt.GetResponse().GetStatus(), + evt.GetResponse().GetStatusText()); + link = evt.GetResponse().GetHeader("Link"); + break; + } + // Request failed + case wxWebRequest::State_Unauthorized: + { + rez.Printf("%s", evt.GetErrorDescription()); + break; + } + case wxWebRequest::State_Failed: + rez.Printf("%s", evt.GetErrorDescription()); + break; + } + }); + error_msg = ""; + request.Start(); + while (request.IsOk() && rez.Len() == 0) + { + wxYield(); + } + m_textResponseTextCtrl->SetLabelText(rez); + if (rez.Len() > 0 && !rez.StartsWith("Loaded")) { + //m_textResponseTextCtrl->SetValue(answer); + error_msg = rez; + } + wxJSONReader reader; + wxJSONValue root; + // now read the JSON text and store it in the 'root' structure + // check for errors before retreiving values... + if (!answer.IsEmpty()) { + response_link = link; + + int numErrors = reader.Parse(answer, &root); + if (numErrors > 0) { + // m_downloadStaticText->SetLabelText("Error parser JSON text response"); + } + } + return root; +} +void ctlGitPanel::CommandBranch(wxString branchName,wxString cmd) { + wxJSONValue r; + wxString ur; + cfg["url"].AsString(ur); + wxString pr; + cfg["project_id"].AsString(pr); + ur = ur + "projects/" + pr + "/repository/branches"; + wxJSONValue rez; + r["branch"] = branchName; + if (cmd == "create") { + r["ref"] = wxString("main"); + rez = execRequest(ur, r, "POST"); + } + else { + rez = execRequest(ur, r, "DELETE"); + + } + +} +void ctlGitPanel::GetBranchList(bool refresh) { + wxJSONValue r; + wxString ur; + cfg["url"].AsString(ur); + wxString pr; + cfg["project_id"].AsString(pr); + ur = ur+"projects/"+pr+"/repository/branches"; + + if (m_Branch_List_Ctrl->GetCount()==0 || refresh) { + m_Branch_List_Ctrl->Clear(); + wxJSONValue rez = execRequest(ur, r,"GET"); + for (int i = 0; i < rez.Size(); i++) { + wxJSONValue b = rez[i]; + wxString name; + b["name"].AsString(name); + m_Branch_List_Ctrl->Append(name); + } + } + int i; + for (i = 0; i < m_Branch_List_Ctrl->GetCount(); i++) { + if (m_Branch_List_Ctrl->GetString(i) == currentDBname) { + m_Branch_List_Ctrl->SetSelection(i); + break; + } + } + if (i >= m_Branch_List_Ctrl->GetCount()) { + CommandBranch(currentDBname, "create"); + m_Branch_List_Ctrl->Append(currentDBname); + m_Branch_List_Ctrl->SetSelection(i); + } + + +} +bool ctlGitPanel::CheckValidObject(pgObject *o) { + if (o ) { + if (o->GetMetaType() == PGM_VIEW + || o->GetMetaType() == PGM_FOREIGNTABLE + || o->GetMetaType() == PGM_FUNCTION + || o->GetMetaType() == PGM_TABLE + || o->GetMetaType() == PGM_TRIGGER + || o->GetMetaType() == PGM_SCHEMA + ) + return true; + } + if (o && !o->IsCollection()) { + + } + return false; +} +void ctlGitPanel::ShowPage(pgObject* data) { + m_textResponseTextCtrl->Clear(); + // wxString path(formMain->GetNodePath(data->GetId())); + + wxString path; + wxString msg = "not support"; + ctlTree* browser= formMain->GetBrowser(); + wxTreeItemId node= data->GetId(); + path = browser->GetItemText(node).BeforeFirst('(').Trim(); + if (data->GetTypeName() == "Procedure" + || data->GetTypeName() == "Function" + || data->GetTypeName() == "Trigger Function" + || data->GetTypeName() == "Event Trigger" + ) + path = browser->GetItemText(node).Trim(); + wxJSONValue r; + wxString typenam= data->GetTypeName(); + bool ex = false; + bool valid = true; + r = cfg["control_objects"]; + if (!data->IsCollection()) { + typenam = data->GetFactory()->GetCollectionFactory()->GetTypeName(); + } + if (!r.IsNull()) { + bool nx = true; + for (int j = 0; j < r.Size(); j++) { + wxString ss = r[j].AsString(); + if (ss == typenam) { nx=false; break; }; + } + if (nx) { valid = false; ex = true; msg = wxString::Format("'%s'not \"control_objects\"", typenam); }; + } + wxTreeItemId parent = browser->GetItemParent(node); + + + while (parent.IsOk()) + { + pgObject* o = browser->GetObject(parent); + if (o) typenam = o->GetTypeName(); + + if (o && (typenam == "Schemas" + || typenam == "Schema" + )) { + //valid = CheckValidObject(data); + if (o && typenam == "Schema") { + r = cfg["ignore_schema"]; + if (!r.IsNull()) { + for (int j = 0; j < r.Size(); j++) { + wxString ss = r[j].AsString(); + if (ss == o->GetName()) { + valid = false; + msg = "schema ignore"; + ex = true; + } + } + + } + } + } + if (typenam == "Database") { + ex = true; + currentDBname = o->GetName(); + nodeDB = o->GetId(); + } + if (o && o->IsCollection()) { + r = cfg["control_objects"]; + if (!r.IsNull()) { + bool nx = true; + for (int j = 0; j < r.Size(); j++) { + wxString ss = r[j].AsString(); + if (ss == typenam) { nx = false; break; }; + } + if (nx) { valid = false; ex = true; msg = wxString::Format("'%s'not \"control_objects\"", typenam); }; + } + + } + path = browser->GetItemText(parent).BeforeFirst('(').Trim() + wxT("/") + path; + o = browser->GetObject(parent); + if (o && o->GetMetaType() == PGM_TABLE && !o->IsCollection()) { + // without partitions + path = browser->GetItemText(parent).BeforeFirst('(').Trim(); + node = parent; + data = o; + } + parent = browser->GetItemParent(parent); + if (ex) break; + } + if (syncDBname != currentDBname) { + valid = false; + msg = wxString::Format("diiference db name and load git db '%s'<>'%s'", currentDBname, syncDBname); + } + if (!valid) { + m_link->SetLabel(msg); + return; + } + wxString stype = data->GetTypeName(); + m_link->SetLabel(path+" - "+ stype); + if (stype=="Schema") return; + if (data->IsCollection()) { + + return; + } + // need load gitlad + if (syncDBname!=currentDBname || currentDBname.IsEmpty()) return; + + wxString sq; + + wxStringToStringHashMap::iterator it; + it = m_treeName.find(path); + if (it == m_treeName.end()) { + m_treeName[path] = "new"; + it = m_treeName.find(path); + } + wxString v = it->second; + sq = data->GetSql(browser); + sq.Replace("\r\n", "\n"); + if (v == "new") { + // add commit + ReplaceItem(path, Files::Add_File); + m_base_content[path] = sq; + + } + else { + wxString s2 = m_git_content[path]; + if (s2 != sq) { + m_treeName[path] = "update"; + m_base_content[path] = sq; + ReplaceItem(path, Files::Update_File); + } + else { + m_treeName[path] = "equal"; + ReplaceItem(path, -1); + } + + } +} +int ctlGitPanel::ReplaceItem(wxString path,int image) { + int i = m_commit_List_View->FindItem(-1, path); + if (i != wxNOT_FOUND) m_commit_List_View->DeleteItem(i); + else i = m_commit_List_View->GetItemCount(); + if (image == -1) return i; + return m_commit_List_View->InsertItem(i, path, image); +} +wxString url_encode(const wxString param) { + + wxCharBuffer utf8CB = param.utf8_str(); + char *readBuff = utf8CB.data(); + size_t len = strlen(readBuff); + wxString encode; + for (int i = 0; i < len;i++) { + char c = readBuff[i]; + if ((c>0) && ((isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~'|| c=='='))) { + encode.Append(c); + continue; + } + encode.Append(wxString::Format("%%%02X", (unsigned char)c)); + } + + return encode; + +} +wxString base64_decode(const wxString enc) { + wxString str; + wxMemoryBuffer buf; + buf = wxBase64Decode(enc); + str = wxString::FromUTF8((char*)buf.GetData(), buf.GetDataLen()); + return str; +} +wxString base64_encode(const wxString dec) { + wxString str; + wxCharBuffer cb; + cb=dec.ToUTF8(); + str = wxBase64Encode(cb.data(),cb.length()); + //str = wxString::FromUTF8((char*)buf.GetData(), buf.GetDataLen()); + return str; +} +wxString ctlGitPanel::getCurBranch(wxString dbname) { + wxJSONValue r = cfg["maps_branch_to_dbname"]; + wxString br; + if (!r.IsNull()) { + for (int j = 0; j < r.Size(); j++) { + wxJSONValue r2 = r[j]; + wxString b= r2["branch"].AsString(); + wxJSONValue r3 = r2["list_db"]; + for (int k = 0; k < r3.Size(); k++) { + if (dbname == r3[k].AsString()) { + br= b; + goto ex_for; + } + } + } + + } + br = dbname; +ex_for: + int i=m_Branch_List_Ctrl->FindString(br); + if (i < 0) { + wxMessageBox("Not found branch for db name = " + dbname); + br = ""; + } + return br; +} +void ctlGitPanel::GetRepositoryTree(wxString branchName, wxString path, wxString typeElement, wxString value) { + wxJSONValue r; + wxString ur; + //m_git_tree[path] = "need load"; + + cfg["url"].AsString(ur); + wxString pr; + cfg["project_id"].AsString(pr); + ur = ur + "projects/" + pr + "/repository/tree"; + wxJSONValue rez; + wxJSONValue prrr; + r["ref"] = getCurBranch(branchName); + wxString rpath = path.AfterFirst('/'); + r["pagination"] = wxString("keyset"); + r["recursive"] = wxString("true"); + r["per_page"] = 90; + r["path"] = wxString(""); + ur = ur + ArgsForGet(r); + m_urlTextCtrl->SetValue(ur); + while (true) + { + + + rez=execRequest(ur, prrr, "GET"); + if (rez.AsArray()) { + wxString prefix = path.BeforeFirst('/'); + for (int j = 0; j < rez.Size(); j++) { + wxJSONValue ss = rez[j]; + wxString name = branchName + "/"+ss["path"].AsString(); + if (ss["type"].AsString() != "tree") { + m_treeName[name] = "git"; + m_count_git++; + } + //if (name==path) m_treeName[path] = "both"; + } + if (!response_link.IsEmpty()) { + int st = 0; + wxString tmp; + while (true) { + int p3 = response_link.find(',',st); + if (p3 < 0) p3 = response_link.Len(); + if (p3 > 0) { + tmp = response_link.substr(st, p3 - st); + int p1 = tmp.Find("rel=\"next"); + if (p1 < 0 && p3 == response_link.Len()) { + return; + }; + if (p1 > 0) { + int e = tmp.Index('>'); + int s = tmp.Index('<'); + ur = tmp.substr(s + 1, e - s - 1); + break; + } + st = p3 + 1; + + } + else break; + } + //ur = response_link; + continue; + } + } + break; + } + return; +} +wxString ctlGitPanel::ArgsForGet(wxJSONValue r) { + wxArrayString key = r.GetMemberNames(); + wxString params; + wxJSONValue rez; + for (int i = 0; i < key.Count(); i++) { + //if (key[i]=="path") + wxString p = key[i] + "=" + r[key[i]].AsString(); + if (params.IsEmpty()) params = "?"; + else params += "&"; + params = params + url_encode(p); + } + + return params; +} + +void ctlGitPanel::OnCommitButton(wxCommandEvent& WXUNUSED(evt)) { + wxJSONValue p,act,f; + wxString br = getCurBranch(currentDBname); + if (br != currentDBname) { + wxMessageBox("Branch name not equals DB name. Commit denied."); + return; + } + p["branch"] = br; + int cnt = 0; + wxString comment = m_CommentTextCtrl->GetValue(); + if (comment.IsEmpty()) { + wxMessageBox("Commit name is Empty"); + return; + } + + p["commit_message"] = comment; + wxArrayInt num; + for (int i = 0; i < m_commit_List_View->GetItemCount(); i++) { + if (!m_commit_List_View->IsSelected(i)) continue; + wxString path = m_commit_List_View->GetItemText(i); + wxString rpath = path.AfterFirst('/'); + if (m_treeName[path] == "update") f["action"] = wxString("update"); + if (m_treeName[path] == "new") f["action"] = wxString("create"); + if (m_treeName[path] == "git") f["action"] = wxString("delete"); + f["file_path"] = rpath; + if (f["action"].AsString() != "delete") { + f["encoding"] = wxString("base64"); + wxString cont = m_base_content[path]; + wxString enc = base64_encode(cont); + f["content"] = enc; + } + act.Append(f); + num.Add(i); + cnt++; + + } + if (cnt > 0) { + p["actions"] = act; + bool re=ApplyCommit(currentDBname, p); + if (re) { + for (int j = num.Count() - 1; j >= 0; j--) { + wxString path = m_commit_List_View->GetItemText(num[j]); + m_commit_List_View->DeleteItem(num[j]); + if (m_treeName[path] == "update") { + m_treeName[path] = "equal"; + m_git_content[path]= m_base_content[path]; + } + else if (m_treeName[path] == "new") { + m_treeName[path] = "equal"; + m_git_content[path] = m_base_content[path]; + } + else if (m_treeName[path] == "git") { + wxStringToStringHashMap::iterator it; + it=m_treeName.find(path); + m_treeName.erase(it); + } + + + + } + } + } +} +bool ctlGitPanel::ApplyCommit(wxString branchName, wxJSONValue params) { + wxJSONValue r; + wxString ur; + cfg["url"].AsString(ur); + wxString pr; + cfg["project_id"].AsString(pr); + ur = ur + "projects/" + pr + "/repository/commits"; + wxJSONValue rez; + // r["pagination"] = wxString("keyset"); + // r["recursive"] = wxString("true"); + // r["path"] = wxString(""); + ur = ur + ArgsForGet(r); + m_urlTextCtrl->SetValue(ur); + rez = execRequest(ur, params, "POST"); + if (!rez.IsNull()) { + wxString u = rez["web_url"].AsString(); + m_link->SetURL(u); + return true; + } + return false; +} +wxString ctlGitPanel::GetRepositoryFile(wxString branchName, wxString path) { + wxJSONValue r; + wxString ur; + //m_git_tree[path] = "need load"; + + cfg["url"].AsString(ur); + wxString pr; + cfg["project_id"].AsString(pr); + wxString rpath = path.AfterFirst('/'); + if (rpath.IsEmpty()) rpath = path; + ur = ur + "projects/" + pr + "/repository/files/"+url_encode(rpath); + wxJSONValue rez; + r["ref"] = getCurBranch(branchName); +// r["pagination"] = wxString("keyset"); +// r["recursive"] = wxString("true"); +// r["path"] = wxString(""); + ur = ur + ArgsForGet(r); + m_urlTextCtrl->SetValue(ur); + rez = execRequest(ur, rez, "GET"); + if (!rez.IsNull()) { + wxString cont = rez["content"].AsString(); + wxString encod= rez["encoding"].AsString(); + wxString str; + if (encod == "base64") { + cont = base64_decode(cont); + } + m_git_content[path] = cont; + return cont; + } + return ""; +} +void ctlGitPanel::GetExpandedChildNodes(wxTreeItemId node, wxArrayString& expandedNodes, wxString pat, int lvl) +{ + wxTreeItemIdValue cookie; + wxTreeItemId child; + ctlTree* browser = formMain->GetBrowser(); + if (lvl == 0) child = node; + else child = browser->GetFirstChild(node, cookie); + pgObject* obj; + wxString path; + time_t tmp; + int size = expandedNodes.Count(); + while (child.IsOk()) + { + obj = browser->GetObject(child); + wxString typenam; + wxString s; + if (obj) { + typenam = obj->GetTypeName(); + wxJSONValue r; + + if (typenam == "Schema") { + r = cfg["ignore_schema"]; + if (!r.IsNull()) { + for (int j = 0; j < r.Size(); j++) { + wxString ss = r[j].AsString(); + if (ss == obj->GetName()) goto nex; + } + + } + } + + if (obj->GetMetaType() == PGM_CATALOG + || obj->GetMetaType() == PGM_COLUMN + || obj->GetMetaType() == PGM_RULE + || obj->GetMetaType() == PGM_CATALOG + ) { + child = browser->GetNextChild(node, cookie); + continue; + } + r = cfg["control_objects"]; + if (!r.IsNull()&& obj->IsCollection()) { + bool nx = true; + for (int j = 0; j < r.Size(); j++) { + wxString ss = r[j].AsString(); + if (ss == typenam) { nx = false; break; }; + } + if (nx) goto nex; + } + + if ((obj->GetMetaType() == PGM_SCHEMA + || obj->GetMetaType() == PGM_DATABASE + || obj->GetMetaType() == PGM_TABLE + || obj->GetMetaType() == PGM_FOREIGNTABLE + ) && !obj->IsCollection()) { + obj->ShowTreeDetail(browser); + //obj->ShowTree(parent,browser); + } + else + { + if (obj->GetMetaType() == PGM_VIEW) obj->ShowTreeDetail(browser); // + if (obj->GetMetaType() == PGM_EVENTTRIGGER) // + obj->ShowTreeDetail(browser); + } + + } + if (browser->HasChildren(child)) + { + bool rec = true; + if (obj && (obj->GetMetaType() == PGM_TABLE + //||obj->GetMetaType()==PGM_VIEW + )) { + wxTreeItemId Item = browser->GetItemParent(child); + obj = browser->GetObject(Item); // Tables + wxTreeItemId Item2 = browser->GetItemParent(obj->GetId()); + obj = browser->GetObject(Item2); // Schemes + if (obj && obj->GetMetaType() == PGM_SCHEMA && !obj->IsCollection()) { + rec = false; // . , + obj = browser->GetObject(child); + obj->ShowTreeDetail(browser); + } + else obj = browser->GetObject(child); + } + if (obj && (obj->GetMetaType() == PGM_VIEW && !obj->IsCollection())) rec = false; + if (rec) { + GetExpandedChildNodes(child, expandedNodes, pat + '/' + browser->GetItemText(child).BeforeFirst('(').Trim(), lvl + 1); + //expandedNodes.Add(parent->GetNodePath(child)); + obj = browser->GetObject(child); + } + + } + if (obj) { + + s = obj->GetSql(browser); + if ((typenam != "Schema" && typenam != "Database")&& !obj->IsCollection()) { + // SQL , Git + wxString p = pat + '/' + browser->GetItemText(child).Trim(); + m_link->SetLabel(p); + m_count_db++; + expandedNodes.Add(p); + s.Replace("\r\n", "\n"); + m_base_content[p] = s; + wxStringToStringHashMap::iterator it; + it = m_treeName.find(p); + if (it != m_treeName.end()) { + wxString v = it->second; + wxString g_path= it->first; + if (v == "git") { + wxString s2 = GetRepositoryFile(currentDBname, p); + if (!s2.IsSameAs(s)) { + m_treeName[p] = "update"; + ReplaceItem(p, Files::Update_File); + } + else + m_treeName[p] = "equal"; + } + } + else { + // not found git + m_treeName[p] = "new"; + ReplaceItem(p, Files::Add_File); + + } + + } + } + +nex: + child = browser->GetNextChild(node, cookie); + } +} +void ctlGitPanel::OnLoadGitButton(wxCommandEvent& WXUNUSED(evt)) +{ + wxTreeItemId node; + wxArrayString expandedNodes; + m_startButton->Disable(); + wxString msg = "Load gitlab db name = " + currentDBname; + syncDBname = ""; + if (nodeDB.IsOk()) { + m_git_content.clear(); + m_base_content.clear(); + m_treeName.clear(); + m_base_tree.clear(); + m_count_git = 0; m_count_db = 0; + //m_commit_List_View + m_commit_List_View->DeleteAllItems(); + if (getCurBranch(currentDBname).IsEmpty()) { + goto ex; + }; + + wxString path = '/'; + GetRepositoryTree(currentDBname,path,"", ""); + if (!error_msg.IsEmpty()) { msg = error_msg; goto ex; } + GetExpandedChildNodes(nodeDB, expandedNodes, currentDBname, 1); + wxStringToStringHashMap::iterator it= m_treeName.begin(); + // only git + for (wxStringToStringHashMap::iterator en = m_treeName.end(); it != en; ++it) + { + path = it->first; + if ((it->second) == "git") { + ReplaceItem(path,Files::Remove_File); + } + } + + for (int i = 0; i < expandedNodes.GetCount(); i++) { + wxString s = expandedNodes[i]; + //m_commit_List_View->InsertItem(m_commit_List_View->GetItemCount(), s, 2); + } + + msg = wxString::Format("%s (git obj %d; db obj %d)",msg, m_count_git, m_count_db); + syncDBname = currentDBname; + } +ex: + m_link->SetLabel(msg); + m_startButton->Enable(); +} + +void ctlGitPanel::OnCancelButton(wxCommandEvent& WXUNUSED(evt)) +{ + if (m_currentRequest.IsOk()) + m_currentRequest.Cancel(); +} + + +void ctlGitPanel::OnProgressTimer(wxTimerEvent& WXUNUSED(evt)) +{ +} + +void ctlGitPanel::OnPostCheckBox(wxCommandEvent& WXUNUSED(evt)) +{ + m_postContentTypeTextCtrl->Enable(m_postCheckBox->IsChecked()); + m_postRequestTextCtrl->Enable(m_postCheckBox->IsChecked()); + wxColour textBg = wxSystemSettings::GetColour( + (m_postCheckBox->IsChecked()) ? wxSYS_COLOUR_WINDOW : wxSYS_COLOUR_BTNFACE); + + m_postContentTypeTextCtrl->SetBackgroundColour(textBg); + m_postRequestTextCtrl->SetBackgroundColour(textBg); +} + + +void ctlGitPanel::OnClose(wxCloseEvent& event) +{ + if (m_currentRequest.IsOk()) + { + if (event.CanVeto()) + { + wxMessageDialog dialog + ( + this, + "A web request is in progress, " + "closing the window will cancel it.", + "Please confirm", + wxYES_NO + ); + dialog.SetYesNoLabels("Cancel and close", "Don't close"); + + if (dialog.ShowModal() != wxID_YES) + { + event.Veto(); + return; + } + } + + m_currentRequest.Cancel(); + } + + event.Skip(); +} + +void ctlGitPanel::OnNotebookPageChanged(wxBookCtrlEvent& event) +{ + //SourceViewDialog* dlg = new SourceViewDialog(NULL, "helo\nworld\n", "helo\nworld right\n3 line\n4 line\n"); + //dlg->Show(); + +} + diff --git a/frm/frmMain.cpp b/frm/frmMain.cpp index 43c863e..0eee4df 100644 --- a/frm/frmMain.cpp +++ b/frm/frmMain.cpp @@ -171,6 +171,9 @@ frmMain::frmMain(const wxString &title) statistics = new ctlListView(listViews, CTL_STATVIEW, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER); dependencies = new ctlListView(listViews, CTL_DEPVIEW, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER); dependents = new ctlListView(listViews, CTL_REFVIEW, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER); + git = NULL; + wxJSONValue cfg=ctlGitPanel::GetConfig(); + if (!cfg.IsNull()) git = new ctlGitPanel(listViews, this,cfg); @@ -183,6 +186,9 @@ frmMain::frmMain(const wxString &title) listViews->AddPage(statistics, _("Statistics")); // NBP_STATISTICS listViews->AddPage(dependencies, _("Dependencies")); // NBP_DEPENDENCIES listViews->AddPage(dependents, _("Dependents")); // NBP_DEPENDENTS + if (git) { + listViews->AddPage(git, _("Git")); // + } properties->SetImageList(imageList, wxIMAGE_LIST_SMALL); statistics->SetImageList(imageList, wxIMAGE_LIST_SMALL); @@ -817,6 +823,12 @@ void frmMain::ShowObjStatistics(pgObject *data, wxWindow *ctrl) data->ShowDependents(this, dependents); dependents->Thaw(); } + if ((!ctrl && git && git->IsShownOnScreen()) || ctrl == git) + { + + //data->ShowDependents(this, dependents); + if (git && data) git->ShowPage(data); + } } diff --git a/include/ctl/ctlGitPanel.h b/include/ctl/ctlGitPanel.h new file mode 100644 index 0000000..634919c --- /dev/null +++ b/include/ctl/ctlGitPanel.h @@ -0,0 +1,111 @@ +#pragma once + +#ifndef _WX_ctlGitPanel_H_ +#define _WX_ctlGitPanel_H_ +#include "wx/wx.h" +#include "wx/notebook.h" +#include "wx/artprov.h" +#include "wx/creddlg.h" +#include "wx/webrequest.h" +#include "wx/filedlg.h" +#include "wx/image.h" +#include "utils/json/jsonval.h" +#include +#include "images/ddAddColumn.pngc" +#include "images/ddRemoveColumn.pngc" +#include "images/conversion.pngc" + + +class ctlGitPanel : public wxPanel +{ +public: + enum Pages + { + Page_Image, + Page_Text, + Page_Download, + Page_Advanced + }; + enum Files + { + Add_File, + Remove_File, + Update_File + }; + + ctlGitPanel(wxWindow* parent, frmMain* form,wxJSONValue cf); + ~ctlGitPanel(); + void ShowPage(pgObject* data); + void getFileRepository(pgObject* data); + wxJSONValue execRequest(wxString url, wxJSONValue args, wxString cmd); + //wxJSONValue setComonnArgs(); + void GetBranchList(bool refresh); + void GetRepositoryTree(wxString branchName,wxString path,wxString typeElement,wxString value); + wxString GetRepositoryFile(wxString branchName, wxString path); + wxString getCurBranch(wxString dbname); + bool CheckValidObject(pgObject *o); + void CommandBranch(wxString branchName, wxString cmd); + bool ApplyCommit(wxString branchName, wxJSONValue params); + wxString ArgsForGet(wxJSONValue r); + int ReplaceItem(wxString path, int image); + static wxJSONValue GetConfig(); + void OnBranchListButton(wxCommandEvent& WXUNUSED(evt)) { + GetBranchList(true); + }; + void GetExpandedChildNodes(wxTreeItemId node, wxArrayString& expandedNodes, wxString pat, int lvl); + void OnBranchDeleteButton(wxCommandEvent& WXUNUSED(evt)) {}; + + void OnListRClick(wxListEvent& evt); + void OnLoadGitButton(wxCommandEvent& WXUNUSED(evt)); + void OnCommitButton(wxCommandEvent& WXUNUSED(evt)); + void OnCancelButton(wxCommandEvent& WXUNUSED(evt)); + void OnKEY_UP(wxKeyEvent& event); + void OnProgressTimer(wxTimerEvent& WXUNUSED(evt)); + void OnPostCheckBox(wxCommandEvent& WXUNUSED(evt)); + void OnNotebookPageChanged(wxBookCtrlEvent& event); + void OnClose(wxCloseEvent& event); + + +private: + frmMain* formMain; + wxJSONValue cfg; + wxString currentDBname; + wxString syncDBname; + wxTreeItemId nodeDB; + wxStringToStringHashMap m_git_content,m_base_content; + wxStringToStringHashMap m_treeName,m_base_tree; + wxString response_link,error_msg; + int m_count_git, m_count_db; + wxListView* m_commit_List_View; + + wxComboBox* m_Branch_List_Ctrl; + wxButton *m_branchDeleteButton, *m_branchListButton, *m_commitButton; + wxHyperlinkCtrl* m_link; + wxNotebook* m_notebook; + wxTextCtrl* m_urlTextCtrl; + wxButton* m_startButton; + wxButton* m_cancelButton; + wxStaticBitmap* m_imageStaticBitmap; + wxWebRequest m_currentRequest; + + wxCheckBox* m_postCheckBox; + wxTextCtrl* m_postContentTypeTextCtrl; + wxTextCtrl* m_postRequestTextCtrl; + wxTextCtrl* m_textResponseTextCtrl; + wxTextCtrl* m_CommentTextCtrl; + wxGauge* m_downloadGauge; + wxStaticText* m_downloadStaticText; + wxTimer m_downloadProgressTimer; + + wxStaticText* m_advCountStaticText; + wxLongLong m_advCount; + + // Normally it would be a bad idea to permanently store credentials like + // this, we should use wxSecretStore to load them as needed, but let's keep + // things simple in this example. + wxWebCredentials m_credentials; + + +}; + +#endif diff --git a/include/frm/frmMain.h b/include/frm/frmMain.h index cbad701..f5ac15f 100644 --- a/include/frm/frmMain.h +++ b/include/frm/frmMain.h @@ -25,6 +25,7 @@ #include "dlg/dlgClasses.h" #include "utils/factory.h" #include "frm/frmLog.h" +#include "ctl/ctlGitPanel.h" // // This number MUST be incremented if changing any of the default perspectives @@ -206,6 +207,7 @@ private: ctlListView *properties; ctlListView *statistics; ctlListView *dependents, *dependencies; + ctlGitPanel* git; ctlAuiNotebook *listViews; ctlSQLBox *sqlPane; wxMenu *newMenu, *debuggingMenu, *reportMenu, *toolsMenu, *pluginsMenu, *viewMenu, diff --git a/include/precomp.h b/include/precomp.h index 8ab4234..b6415cb 100644 --- a/include/precomp.h +++ b/include/precomp.h @@ -247,5 +247,8 @@ #include "utils/sysProcess.h" #include "utils/sysSettings.h" #include "utils/utffile.h" +#include "utils/json/jsonval.h" +#include "utils/json/jsonreader.h" +#include "utils/json/jsonwriter.h" #endif #endif diff --git a/include/utils/json/json_defs.h b/include/utils/json/json_defs.h new file mode 100644 index 0000000..55a7d28 --- /dev/null +++ b/include/utils/json/json_defs.h @@ -0,0 +1,209 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: json_defs.h +// Purpose: shared build defines +// Author: Luciano Cattani +// Created: 2007/10/20 +// RCS-ID: $Id: json_defs.h,v 1.6 2008/03/12 10:48:19 luccat Exp $ +// Copyright: (c) 2007 Luciano Cattani +// Licence: wxWidgets licence +///////////////////////////////////////////////////////////////////////////// + + +#ifndef _WX_JSON_DEFS_H_ +#define _WX_JSON_DEFS_H_ + +// Defines for component version. +// The following symbols should be updated for each new component release +// since some kind of tests, like those of AM_WXCODE_CHECKFOR_COMPONENT_VERSION() +// for "configure" scripts under unix, use them. +#define wxJSON_MAJOR 1 +#define wxJSON_MINOR 2 +#define wxJSON_RELEASE 1 + +// For non-Unix systems (i.e. when building without a configure script), +// users of this component can use the following macro to check if the +// current version is at least major.minor.release +#define wxCHECK_JSON_VERSION(major,minor,release) \ + (wxJSON_MAJOR > (major) || \ + (wxJSON_MAJOR == (major) && wxJSON_MINOR > (minor)) || \ + (wxJSON_MAJOR == (major) && wxJSON_MINOR == (minor) && wxJSON_RELEASE >= (release))) + + +// Defines for shared builds. +// Simple reference for using these macros and for writin components +// which support shared builds: +// +// 1) use the WXDLLIMPEXP_MYCOMP in each class declaration: +// class WXDLLIMPEXP_MYCOMP myCompClass { [...] }; +// +// 2) use the WXDLLIMPEXP_MYCOMP in the declaration of each global function: +// WXDLLIMPEXP_MYCOMP int myGlobalFunc(); +// +// 3) use the WXDLLIMPEXP_DATA_MYCOMP() in the declaration of each global +// variable: +// WXDLLIMPEXP_DATA_MYCOMP(int) myGlobalIntVar; +// +#ifdef WXMAKINGDLL_JSON +#define WXDLLIMPEXP_JSON WXEXPORT +#define WXDLLIMPEXP_DATA_JSON(type) WXEXPORT type +#elif defined(WXUSINGDLL11) +#define WXDLLIMPEXP_JSON WXIMPORT +#define WXDLLIMPEXP_DATA_JSON(type) WXIMPORT type +#else // not making nor using DLL +#define WXDLLIMPEXP_JSON +#define WXDLLIMPEXP_DATA_JSON(type) type +#endif + +// the __PRETTY_FUNCTION__ macro expands to the full class's +// member name in the GNU GCC. +// For other compilers we use the standard __wxFUNCTION__ macro +#if !defined( __GNUC__ ) +#define __PRETTY_FUNCTION__ __WXFUNCTION__ +#endif + + + +// define wxJSON_USE_UNICODE if wxWidgets was built with +// unicode support +#if defined( wxJSON_USE_UNICODE ) +#undef wxJSON_USE_UNICODE +#endif +// do not modify the following lines +#if wxUSE_UNICODE == 1 +#define wxJSON_USE_UNICODE +#endif + +// the following macro, if defined, cause the wxJSONValue to store +// pointers to C-strings as pointers to statically allocated +// C-strings. By default this macro is not defined +// #define wxJSON_USE_CSTRING + + +// the following macro, if defined, cause the wxJSONvalue and its +// referenced data structure to store and increment a static +// progressive counter in the ctor. +// this is only usefull for debugging purposes +// #define WXJSON_USE_VALUE_COUNTER + + +// the following macro is used by wxJSON internally and you should not +// modify it. If the platform seems to support 64-bits integers, +// the following lines define the 'wxJSON_64BIT_INT' macro +#if defined( wxLongLong_t ) +#define wxJSON_64BIT_INT +#endif + + +// +// the following macro, if defined, cause the wxJSON library to +// always use 32-bits integers also when the platform seems to +// have native 64-bits support: by default the macro if not defined +// +// #define wxJSON_NO_64BIT_INT +// +#if defined( wxJSON_NO_64BIT_INT ) && defined( wxJSON_64BIT_INT ) +#undef wxJSON_64BIT_INT +#endif + +// +// it seems that some compilers do not define 'long long int' limits +// constants. For example, this is the output of the Borland BCC 5.5 +// compiler when I tried to compile wxJSON with 64-bits integer support: +// Error E2451 ..\src\jsonreader.cpp 1737: Undefined symbol 'LLONG_MAX' +// in function wxJSONReader::Strtoll(const wxString &,__int64 *) +// *** 1 errors in Compile *** +// so, if the constants are not defined, I define them by myself +#if !defined( LLONG_MAX ) +#define LLONG_MAX 9223372036854775807 +#endif + +#if !defined( ULLONG_MAX ) +#define ULLONG_MAX 18446744073709551615 +#endif + +#if !defined( LLONG_MIN ) +#define LLONG_MIN -9223372036854775808 +#endif + + + +// the same applies for all other integer constants +#if !defined( INT_MIN ) +#define INT_MIN -32768 +#endif +#if !defined( INT_MAX ) +#define INT_MAX 32767 +#endif +#if !defined( UINT_MAX ) +#define UINT_MAX 65535 +#endif +#if !defined( LONG_MIN ) +#define LONG_MIN -2147483648 +#endif +#if !defined( LONG_MAX ) +#define LONG_MAX 2147483647 +#endif +#if !defined( ULONG_MAX ) +#define ULONG_MAX 4294967295 +#endif +#if !defined( SHORT_MAX ) +#define SHORT_MAX 32767 +#endif +#if !defined( SHORT_MIN ) +#define SHORT_MIN -32768 +#endif +#if !defined( USHORT_MAX ) +#define USHORT_MAX 65535 +#endif + + + +// +// define the wxJSON_ASSERT() macro to expand to wxASSERT() +// unless the wxJSON_NOABORT_ASSERT is defined +// #define wxJSON_NOABORT_ASSERT +#if defined( wxJSON_NOABORT_ASSERT ) +#define wxJSON_ASSERT( cond ) +#else +#define wxJSON_ASSERT( cond ) wxASSERT( cond ); +#endif + + +// +// the following macros are used by the wxJSONWriter::WriteStringValues() +// when the wxJSONWRITER_SPLIT_STRING flag is set +#define wxJSONWRITER_LAST_COL 50 +#define wxJSONWRITER_SPLIT_COL 75 +#define wxJSONWRITER_MIN_LENGTH 15 +#define wxJSONWRITER_TAB_LENGTH 4 + + +// +// some compilers (i.e. MSVC++) defines their own 'snprintf' function +// so if it is not defined, define it in the following lines +// please note that we cannot use the wxWidget's counterpart 'wxSnprintf' +// because the latter uses 'wxChar' but wxJSON only use 'char' +#if !defined(snprintf) && defined(_MSC_VER) +#define snprintf _snprintf +#endif + + +// +// check if wxWidgets is compiled using --enable-stl in which case +// we have to use different aproaches when declaring the array and +// key/value containers (see the docs: wxJSON internals: array and hash_map +#undef wxJSON_USE_STL +#if defined( wxUSE_STL ) && wxUSE_STL == 1 +#define wxJSON_USE_STL +#endif + +// +// defines the MIN and MAX macro for numeric arguments +// note that the safest way to define such functions is using templates +#define MIN(a,b) a < b ? a : b +#define MAX(a,b) a > b ? a : b + + +#endif // _WX_JSON_DEFS_H_ + + diff --git a/include/utils/json/jsonreader.h b/include/utils/json/jsonreader.h new file mode 100644 index 0000000..dff55e2 --- /dev/null +++ b/include/utils/json/jsonreader.h @@ -0,0 +1,148 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: jsonreader.h +// Purpose: the parser of JSON text +// Author: Luciano Cattani +// Created: 2007/09/15 +// RCS-ID: $Id: jsonreader.h,v 1.3 2008/03/03 19:05:45 luccat Exp $ +// Copyright: (c) 2007 Luciano Cattani +// Licence: wxWidgets licence +///////////////////////////////////////////////////////////////////////////// + +#include "pgAdmin3.h" + +#if !defined( _WX_JSONREADER_H ) +#define _WX_JSONREADER_H + +#ifdef __GNUG__ +#pragma interface "jsonreader.h" +#endif +// For compilers that support precompilation, includes "wx/wx.h". + +#ifdef __BORLANDC__ +#pragma hdrstop +#endif + +// for all others, include the necessary headers (this file is usually all you +// need because it includes almost all "standard" wxWidgets headers) +#ifndef WX_PRECOMP +#include +#include +#include +#endif + + + +// The flags of the parser +enum { + wxJSONREADER_STRICT = 0, + wxJSONREADER_ALLOW_COMMENTS = 1, + wxJSONREADER_STORE_COMMENTS = 2, + wxJSONREADER_CASE = 4, + wxJSONREADER_MISSING = 8, + wxJSONREADER_MULTISTRING = 16, + wxJSONREADER_COMMENTS_AFTER = 32, + wxJSONREADER_NOUTF8_STREAM = 64, + wxJSONREADER_MEMORYBUFF = 128, + + wxJSONREADER_TOLERANT = wxJSONREADER_ALLOW_COMMENTS | wxJSONREADER_CASE | + wxJSONREADER_MISSING | wxJSONREADER_MULTISTRING, + wxJSONREADER_COMMENTS_BEFORE = wxJSONREADER_ALLOW_COMMENTS | wxJSONREADER_STORE_COMMENTS +}; + + +class WXDLLIMPEXP_JSON wxJSONReader +{ +public: + wxJSONReader(int flags = wxJSONREADER_TOLERANT, int maxErrors = 30); + virtual ~wxJSONReader(); + + int Parse(const wxString& doc, wxJSONValue* val); + int Parse(wxInputStream& doc, wxJSONValue* val); + + int GetDepth() const; + int GetErrorCount() const; + int GetWarningCount() const; + const wxArrayString& GetErrors() const; + const wxArrayString& GetWarnings() const; + + static int UTF8NumBytes(char ch); + +#if defined( wxJSON_64BIT_INT ) + static bool Strtoll(const wxString& str, wxInt64* i64); + static bool Strtoull(const wxString& str, wxUint64* ui64); + static bool DoStrto_ll(const wxString& str, wxUint64* ui64, wxChar* sign); +#endif + +protected: + + int DoRead(wxInputStream& doc, wxJSONValue& val); + void AddError(const wxString& descr); + void AddError(const wxString& fmt, const wxString& str); + void AddError(const wxString& fmt, wxChar ch); + void AddWarning(int type, const wxString& descr); + int GetStart(wxInputStream& is); + int ReadChar(wxInputStream& is); + int PeekChar(wxInputStream& is); + void StoreValue(int ch, const wxString& key, wxJSONValue& value, wxJSONValue& parent); + int SkipWhiteSpace(wxInputStream& is); + int SkipComment(wxInputStream& is); + void StoreComment(const wxJSONValue* parent); + int ReadString(wxInputStream& is, wxJSONValue& val); + int ReadToken(wxInputStream& is, int ch, wxString& s); + int ReadValue(wxInputStream& is, int ch, wxJSONValue& val); + int ReadUES(wxInputStream& is, char* uesBuffer); + int AppendUES(wxMemoryBuffer& utf8Buff, const char* uesBuffer); + int NumBytes(char ch); + int ConvertCharByChar(wxString& s, const wxMemoryBuffer& utf8Buffer); + int ReadMemoryBuff(wxInputStream& is, wxJSONValue& val); + + //! Flag that control the parser behaviour, + int m_flags; + + //! Maximum number of errors stored in the error's array + int m_maxErrors; + + //! The current line number (start at 1). + int m_lineNo; + + //! The current column number (start at 1). + int m_colNo; + + //! The current level of object/array annidation (start at ZERO). + int m_level; + + //! The depth level of the read JSON text + int m_depth; + + //! The pointer to the value object that is being read. + wxJSONValue* m_current; + + //! The pointer to the value object that was last stored. + wxJSONValue* m_lastStored; + + //! The pointer to the value object that will be read. + wxJSONValue* m_next; + + //! The comment string read by SkipComment(). + wxString m_comment; + + //! The starting line of the comment string. + int m_commentLine; + + //! The array of error messages. + wxArrayString m_errors; + + //! The array of warning messages. + wxArrayString m_warnings; + + //! The character read by the PeekChar() function (-1 none) + int m_peekChar; + + //! ANSI: do not convert UTF-8 strings + bool m_noUtf8; +}; + + +#endif // not defined _WX_JSONREADER_H + + diff --git a/include/utils/json/jsonval.h b/include/utils/json/jsonval.h new file mode 100644 index 0000000..08a5845 --- /dev/null +++ b/include/utils/json/jsonval.h @@ -0,0 +1,440 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: jsonval.h +// Purpose: the wxJSONValue class: it holds a JSON value +// Author: Luciano Cattani +// Created: 2007/09/15 +// RCS-ID: $Id: jsonval.h,v 1.4 2008/01/10 21:27:15 luccat Exp $ +// Copyright: (c) 2007 Luciano Cattani +// Licence: wxWidgets licence +///////////////////////////////////////////////////////////////////////////// + +#if !defined( _WX_JSONVAL_H ) +#define _WX_JSONVAL_H + +#ifdef __GNUG__ +#pragma interface "jsonval.h" +#endif + +// For compilers that support precompilation, includes "wx/wx.h". + +#ifdef __BORLANDC__ +#pragma hdrstop +#endif + +// for all others, include the necessary headers (this file is usually all you +// need because it includes almost all "standard" wxWidgets headers) +#ifndef WX_PRECOMP +#include +#include +#include +#include +#endif + + +#include "utils/json/json_defs.h" + +// forward declarations +class WXDLLIMPEXP_JSON wxJSONReader; +class WXDLLIMPEXP_JSON wxJSONRefData; + +#if defined( wxJSON_USE_STL ) +// if compiling on MinGW we use the STL-style declaration of wxWidget's +// container classes +class WXDLLIMPEXP_JSON wxJSONValue; +WX_DECLARE_OBJARRAY(wxJSONValue, wxJSONInternalArray); +WX_DECLARE_STRING_HASH_MAP(wxJSONValue, wxJSONInternalMap); +#else +class WXDLLIMPEXP_JSON wxJSONInternalMap; +class WXDLLIMPEXP_JSON wxJSONInternalArray; +#endif + + +//! The type of the value held by the wxJSONRefData class +enum wxJSONType { + wxJSONTYPE_INVALID = 0, /*!< the object is not uninitialized */ + wxJSONTYPE_NULL, /*!< the object contains a NULL value */ + wxJSONTYPE_INT, /*!< the object contains an integer */ + wxJSONTYPE_UINT, /*!< the object contains an unsigned integer */ + wxJSONTYPE_DOUBLE, /*!< the object contains a double */ + wxJSONTYPE_STRING, /*!< the object contains a wxString object */ + wxJSONTYPE_CSTRING, /*!< the object contains a static C-string */ + wxJSONTYPE_BOOL, /*!< the object contains a boolean */ + wxJSONTYPE_ARRAY, /*!< the object contains an array of values */ + wxJSONTYPE_OBJECT, /*!< the object contains a map of keys/values */ + wxJSONTYPE_LONG, /*!< the object contains a 32-bit integer */ + wxJSONTYPE_INT64, /*!< the object contains a 64-bit integer */ + wxJSONTYPE_ULONG, /*!< the object contains an unsigned 32-bit integer */ + wxJSONTYPE_UINT64, /*!< the object contains an unsigned 64-bit integer */ + wxJSONTYPE_SHORT, /*!< the object contains a 16-bit integer */ + wxJSONTYPE_USHORT, /*!< the object contains a 16-bit unsigned integer */ + wxJSONTYPE_MEMORYBUFF /*!< the object contains a binary memory buffer */ +}; + +// the comment position: every value only has one comment position +// althrough comments may be splitted into several lines +enum { + wxJSONVALUE_COMMENT_DEFAULT = 0, + wxJSONVALUE_COMMENT_BEFORE, + wxJSONVALUE_COMMENT_AFTER, + wxJSONVALUE_COMMENT_INLINE, +}; + +/*********************************************************************** + + class wxJSONValue + +***********************************************************************/ + + +// class WXDLLIMPEXP_JSON wxJSONValue : public wxObject +class WXDLLIMPEXP_JSON wxJSONValue +{ + friend class wxJSONReader; + +public: + + // ctors and dtor + wxJSONValue(); + wxJSONValue(wxJSONType type); + wxJSONValue(int i); + wxJSONValue(unsigned int i); + wxJSONValue(short i); + wxJSONValue(unsigned short i); + wxJSONValue(long int i); + wxJSONValue(unsigned long int i); +#if defined( wxJSON_64BIT_INT) + wxJSONValue(wxInt64 i); + wxJSONValue(wxUint64 ui); +#endif + wxJSONValue(bool b); + wxJSONValue(double d); + wxJSONValue(const wxChar* str); // assume static ASCIIZ strings + wxJSONValue(const wxString& str); + wxJSONValue(const wxMemoryBuffer& buff); + wxJSONValue(const void* buff, size_t len); + wxJSONValue(const wxJSONValue& other); + virtual ~wxJSONValue(); + + // functions for retrieving the value type + wxJSONType GetType() const; + bool IsValid() const; + bool IsNull() const; + bool IsInt() const; + bool IsUInt() const; + bool IsShort() const; + bool IsUShort() const; + bool IsLong() const; + bool IsULong() const; +#if defined( wxJSON_64BIT_INT) + bool IsInt32() const; + bool IsInt64() const; + bool IsUInt32() const; + bool IsUInt64() const; +#endif + bool IsBool() const; + bool IsDouble() const; + bool IsString() const; + bool IsCString() const; + bool IsArray() const; + bool IsObject() const; + bool IsMemoryBuff() const; + + // function for retireving the value as ... + int AsInt() const; + unsigned int AsUInt() const; + short AsShort() const; + unsigned short AsUShort() const; + long int AsLong() const; + unsigned long AsULong() const; + bool AsInt(int& i) const; + bool AsUInt(unsigned int& ui) const; + bool AsShort(short int& s) const; + bool AsUShort(unsigned short& us) const; + bool AsLong(long int& l) const; + bool AsULong(unsigned long& ul) const; +#if defined( wxJSON_64BIT_INT) + wxInt32 AsInt32() const; + wxUint32 AsUInt32() const; + wxInt64 AsInt64() const; + wxUint64 AsUInt64() const; + bool AsInt32(wxInt32& i32) const; + bool AsUInt32(wxUint32& ui32) const; + bool AsInt64(wxInt64& i64) const; + bool AsUInt64(wxUint64& ui64) const; +#endif + bool AsBool() const; + double AsDouble() const; + wxString AsString() const; + const wxChar* AsCString() const; + bool AsBool(bool& b) const; + bool AsDouble(double& d) const; + bool AsString(wxString& str) const; + bool AsCString(wxChar* ch) const; + wxMemoryBuffer AsMemoryBuff() const; + bool AsMemoryBuff(wxMemoryBuffer& buff) const; + + const wxJSONInternalMap* AsMap() const; + const wxJSONInternalArray* AsArray() const; + + // get members names, size and other info + bool HasMember(unsigned index) const; + bool HasMember(const wxString& key) const; + int Size() const; + wxArrayString GetMemberNames() const; + + // appending items, resizing and deleting items + wxJSONValue& Append(const wxJSONValue& value); + wxJSONValue& Append(bool b); + wxJSONValue& Append(int i); + wxJSONValue& Append(unsigned int ui); + wxJSONValue& Append(short int i); + wxJSONValue& Append(unsigned short int ui); + wxJSONValue& Append(long int l); + wxJSONValue& Append(unsigned long int ul); +#if defined( wxJSON_64BIT_INT ) + wxJSONValue& Append(wxInt64 i); + wxJSONValue& Append(wxUint64 ui); +#endif + wxJSONValue& Append(double d); + wxJSONValue& Append(const wxChar* str); + wxJSONValue& Append(const wxString& str); + wxJSONValue& Append(const wxMemoryBuffer& buff); + wxJSONValue& Append(const void* buff, size_t len); + bool Remove(int index); + bool Remove(const wxString& key); + void Clear(); + bool Cat(const wxChar* str); + bool Cat(const wxString& str); + bool Cat(const wxMemoryBuffer& buff); + + // retrieve an item + wxJSONValue& Item(unsigned index); + wxJSONValue& Item(const wxString& key); + wxJSONValue ItemAt(unsigned index) const; + wxJSONValue ItemAt(const wxString& key) const; + + wxJSONValue& operator [] (unsigned index); + wxJSONValue& operator [] (const wxString& key); + + wxJSONValue& operator = (int i); + wxJSONValue& operator = (unsigned int ui); + wxJSONValue& operator = (short int i); + wxJSONValue& operator = (unsigned short int ui); + wxJSONValue& operator = (long int l); + wxJSONValue& operator = (unsigned long int ul); +#if defined( wxJSON_64BIT_INT ) + wxJSONValue& operator = (wxInt64 i); + wxJSONValue& operator = (wxUint64 ui); +#endif + wxJSONValue& operator = (bool b); + wxJSONValue& operator = (double d); + wxJSONValue& operator = (const wxChar* str); + wxJSONValue& operator = (const wxString& str); + wxJSONValue& operator = (const wxMemoryBuffer& buff); + // wxJSONValue& operator = ( const void* buff, size_t len ); cannot be declared + wxJSONValue& operator = (const wxJSONValue& value); + + // get the value or a default value + wxJSONValue Get(const wxString& key, const wxJSONValue& defaultValue) const; + + // comparison function + bool IsSameAs(const wxJSONValue& other) const; + + // comment-related functions + int AddComment(const wxString& str, int position = wxJSONVALUE_COMMENT_DEFAULT); + int AddComment(const wxArrayString& comments, int position = wxJSONVALUE_COMMENT_DEFAULT); + wxString GetComment(int idx = -1) const; + int GetCommentPos() const; + int GetCommentCount() const; + void ClearComments(); + const wxArrayString& GetCommentArray() const; + + // debugging functions + wxString GetInfo() const; + wxString Dump(bool deep = false, int mode = 0) const; + + //misc functions + wxJSONRefData* GetRefData() const; + wxJSONRefData* SetType(wxJSONType type); + int GetLineNo() const; + void SetLineNo(int num); + + // public static functions: mainly used for debugging + static wxString TypeToString(wxJSONType type); + static wxString MemoryBuffToString(const wxMemoryBuffer& buff, size_t len = -1); + static wxString MemoryBuffToString(const void* buff, size_t len, size_t actualLen = -1); + static int CompareMemoryBuff(const wxMemoryBuffer& buff1, const wxMemoryBuffer& buff2); + static int CompareMemoryBuff(const wxMemoryBuffer& buff1, const void* buff2); + static wxMemoryBuffer ArrayToMemoryBuff(const wxJSONValue& value); + +protected: + wxJSONValue* Find(unsigned index) const; + wxJSONValue* Find(const wxString& key) const; + void DeepCopy(const wxJSONValue& other); + + wxJSONRefData* Init(wxJSONType type); + wxJSONRefData* COW(); + + // overidden from wxObject + virtual wxJSONRefData* CloneRefData(const wxJSONRefData* data) const; + virtual wxJSONRefData* CreateRefData() const; + + void SetRefData(wxJSONRefData* data); + void Ref(const wxJSONValue& clone); + void UnRef(); + void UnShare(); + void AllocExclusive(); + + //! the referenced data + wxJSONRefData* m_refData; + + + // used for debugging purposes: only in debug builds. +#if defined( WXJSON_USE_VALUE_COUNTER ) + int m_progr; + static int sm_progr; +#endif +}; + + +#if !defined( wxJSON_USE_STL ) +// if using wxWidget's implementation of container classes we declare +// the OBJARRAY are HASH_MAP _after_ the wxJSONValue is fully known +WX_DECLARE_OBJARRAY(wxJSONValue, wxJSONInternalArray); +WX_DECLARE_STRING_HASH_MAP(wxJSONValue, wxJSONInternalMap); +#endif + + +/*********************************************************************** + + class wxJSONRefData + +***********************************************************************/ + + + + +//! The actual value held by the wxJSONValue class (internal use) +/*! + Note that this structure is a \b union as in versions prior to 0.4.x + The union just stores primitive types and not complex types which are + stored in separate data members of the wxJSONRefData structure. + + This organization give us more flexibility when retrieving compatible + types such as ints unsigned ints, long and so on. + To know more about the internal structure of the wxJSONValue class + see \ref pg_json_internals. +*/ +union wxJSONValueHolder { + int m_valInt; + unsigned int m_valUInt; + short int m_valShort; + unsigned short m_valUShort; + long int m_valLong; + unsigned long m_valULong; + double m_valDouble; + const wxChar* m_valCString; + bool m_valBool; +#if defined( wxJSON_64BIT_INT ) + wxInt64 m_valInt64; + wxUint64 m_valUInt64; +#endif +}; + +// +// access to the (unsigned) integer value is done through +// the VAL_INT macro which expands to the 'long' integer +// data member of the 'long long' integer if 64-bits integer +// support is enabled +#if defined( wxJSON_64BIT_INT ) +#define VAL_INT m_valInt64 +#define VAL_UINT m_valUInt64 +#else +#define VAL_INT m_valLong +#define VAL_UINT m_valULong +#endif + + + +// class WXDLLIMPEXP_JSON wxJSONRefData : public wxObjectRefData +class WXDLLIMPEXP_JSON wxJSONRefData +{ + // friend class wxJSONReader; + friend class wxJSONValue; + friend class wxJSONWriter; + +public: + + wxJSONRefData(); + virtual ~wxJSONRefData(); + + int GetRefCount() const; + + // there is no need to define copy ctor + + //! the references count + int m_refCount; + + //! The actual type of the value held by this object. + wxJSONType m_type; + + //! The JSON value held by this object. + /*! + This data member contains the JSON data types defined by the + JSON syntax with the exception of the complex objects. + This data member is an union of the primitive types + so that it is simplier to cast them in other compatible types. + */ + wxJSONValueHolder m_value; + + //! The JSON string value. + wxString m_valString; + + //! The JSON array value. + wxJSONInternalArray m_valArray; + + //! The JSON object value. + wxJSONInternalMap m_valMap; + + //! The position of the comment line(s), if any. + /*! + The data member contains one of the following constants: + \li \c wxJSONVALUE_COMMENT_BEFORE + \li \c wxJSONVALUE_COMMENT_AFTER + \li \c wxJSONVALUE_COMMENT_INLINE + */ + int m_commentPos; + + //! The array of comment lines; may be empty. + wxArrayString m_comments; + + //! The line number when this value was read + /*! + This data member is used by the wxJSONReader class and it is + used to store the line number of the JSON text document where + the value appeared. This value is compared to the line number + of a comment line in order to obtain the value which a + comment refersto. + */ + int m_lineNo; + + //! The pointer to the memory buffer object + /*! + Note that despite using reference counting, the \b wxMemoryBuffer is not a + \e copy-on-write structure so the wxJSON library uses some tricks in order to + avoid the side effects of copying / assigning wxMemoryBuffer objects + */ + wxMemoryBuffer* m_memBuff; + + // used for debugging purposes: only in debug builds. +#if defined( WXJSON_USE_VALUE_COUNTER ) + int m_progr; + static int sm_progr; +#endif +}; + + + +#endif // not defined _WX_JSONVAL_H + + diff --git a/include/utils/json/jsonwriter.h b/include/utils/json/jsonwriter.h new file mode 100644 index 0000000..755f15f --- /dev/null +++ b/include/utils/json/jsonwriter.h @@ -0,0 +1,121 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: jsonwriter.h +// Purpose: the generator of JSON text from a JSON value +// Author: Luciano Cattani +// Created: 2007/09/15 +// RCS-ID: $Id: jsonwriter.h,v 1.4 2008/03/03 19:05:45 luccat Exp $ +// Copyright: (c) 2007 Luciano Cattani +// Licence: wxWidgets licence +///////////////////////////////////////////////////////////////////////////// + +#include "pgAdmin3.h" + + +#if !defined( _WX_JSONWRITER_H ) +#define _WX_JSONWRITER_H + +#ifdef __GNUG__ +#pragma interface "jsonwriter.h" +#endif + +// For compilers that support precompilation, includes "wx/wx.h". + +#ifdef __BORLANDC__ +#pragma hdrstop +#endif + +// for all others, include the necessary headers (this file is usually all you +// need because it includes almost all "standard" wxWidgets headers) +#ifndef WX_PRECOMP +#include +#include +#endif + +#include "utils/json/json_defs.h" +#include "utils/json/jsonval.h" + +enum { + wxJSONWRITER_NONE = 0, + wxJSONWRITER_STYLED = 1, + wxJSONWRITER_WRITE_COMMENTS = 2, + wxJSONWRITER_COMMENTS_BEFORE = 4, + wxJSONWRITER_COMMENTS_AFTER = 8, + wxJSONWRITER_SPLIT_STRING = 16, + wxJSONWRITER_NO_LINEFEEDS = 32, + wxJSONWRITER_ESCAPE_SOLIDUS = 64, + wxJSONWRITER_MULTILINE_STRING = 128, + wxJSONWRITER_RECOGNIZE_UNSIGNED = 256, + wxJSONWRITER_TAB_INDENT = 512, + wxJSONWRITER_NO_INDENTATION = 1024, + wxJSONWRITER_NOUTF8_STREAM = 2048, + wxJSONWRITER_MEMORYBUFF = 4096 +}; + +// class declaration + +class WXDLLIMPEXP_JSON wxJSONWriter +{ +public: + wxJSONWriter(int style = wxJSONWRITER_STYLED, int indent = 0, int step = 3); + ~wxJSONWriter(); + + void Write(const wxJSONValue& value, wxString& str); + void Write(const wxJSONValue& value, wxOutputStream& os); + void SetDoubleFmtString(const char* fmt); + +protected: + + int DoWrite(wxOutputStream& os, const wxJSONValue& value, const wxString* key, bool comma); + int WriteIndent(wxOutputStream& os); + int WriteIndent(wxOutputStream& os, int num); + bool IsSpace(wxChar ch); + bool IsPunctuation(wxChar ch); + + int WriteString(wxOutputStream& os, const wxString& str); + int WriteStringValue(wxOutputStream& os, const wxString& str); + int WriteNullValue(wxOutputStream& os); + int WriteIntValue(wxOutputStream& os, const wxJSONValue& v); + int WriteUIntValue(wxOutputStream& os, const wxJSONValue& v); + int WriteBoolValue(wxOutputStream& os, const wxJSONValue& v); + int WriteDoubleValue(wxOutputStream& os, const wxJSONValue& v); + int WriteMemoryBuff(wxOutputStream& os, const wxMemoryBuffer& buff); + + int WriteInvalid(wxOutputStream& os); + int WriteSeparator(wxOutputStream& os); + + int WriteKey(wxOutputStream& os, const wxString& key); + int WriteComment(wxOutputStream& os, const wxJSONValue& value, bool indent); + + int WriteError(const wxString& err); + +private: + //! The style flag is a combination of wxJSONWRITER_(something) constants. + int m_style; + + //! The initial indentation value, in number of spaces. + int m_indent; + + //! The indentation increment, in number of spaces. + int m_step; + + //! JSON value objects can be nested; this is the level of annidation (used internally). + int m_level; + + // The line number when printing JSON text output (not yet used) + int m_lineNo; + + // The column number when printing JSON text output + int m_colNo; + + // Flag used in ANSI mode that controls UTF-8 conversion + bool m_noUtf8; + + // The format string for printing doubles + char* m_fmt; +}; + + +#endif // not defined _WX_JSONWRITER_H + + + diff --git a/pgAdmin3.vcxproj b/pgAdmin3.vcxproj index 0e628d5..f09d33d 100644 --- a/pgAdmin3.vcxproj +++ b/pgAdmin3.vcxproj @@ -745,6 +745,7 @@ + @@ -1021,6 +1022,9 @@ + + + @@ -1548,6 +1552,7 @@ + @@ -1565,6 +1570,10 @@ + + + + @@ -2250,6 +2259,7 @@ + diff --git a/pgAdmin3.vcxproj.filters b/pgAdmin3.vcxproj.filters index c614b59..91f222f 100644 --- a/pgAdmin3.vcxproj.filters +++ b/pgAdmin3.vcxproj.filters @@ -1656,6 +1656,18 @@ frm + + utils + + + utils + + + utils + + + ctl + @@ -3560,6 +3572,21 @@ include\frm + + include\utils + + + include\utils + + + include\utils + + + include\utils + + + include\ctl + @@ -4513,6 +4540,9 @@ include\images + + include\images + diff --git a/utils/json/jsonreader.cpp b/utils/json/jsonreader.cpp new file mode 100644 index 0000000..62adcaa --- /dev/null +++ b/utils/json/jsonreader.cpp @@ -0,0 +1,2131 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: jsonreader.cpp +// Purpose: the wxJSONReader class: a JSON text parser +// Author: Luciano Cattani +// Created: 2007/10/14 +// RCS-ID: $Id: jsonreader.cpp,v 1.12 2008/03/12 10:48:19 luccat Exp $ +// Copyright: (c) 2007 Luciano Cattani +// Licence: wxWidgets licence +///////////////////////////////////////////////////////////////////////////// + + +#ifdef __GNUG__ +#pragma implementation "jsonreader.cpp" +#endif + +#include "pgadmin3.h" + +#include +#include +#include +#include + +#define wxJSON_USE_UNICODE 1 + +/*! \class wxJSONReader + \brief The JSON parser + + The class is a JSON parser which reads a JSON formatted text and stores + values in the \c wxJSONValue structure. + The ctor accepts two parameters: the \e style flag, which controls how + much error-tolerant should the parser be and an integer which is + the maximum number of errors and warnings that have to be reported + (the default is 30). + + If the JSON text document does not contain an open/close JSON character the + function returns an \b invalid value object; in other words, the + wxJSONValue::IsValid() function returns FALSE. + This is the case of a document that is empty or contains only + whitespaces or comments. + If the document contains a starting object/array character immediatly + followed by a closing object/array character + (i.e.: \c {} ) then the function returns an \b empty array or object + JSON value. + This is a valid JSON object of type wxJSONTYPE_OBJECT or wxJSONTYPE_ARRAY + whose wxJSONValue::Size() function returns ZERO. + + \par JSON text + + The wxJSON parser just skips all characters read from the + input JSON text until the start-object '{' or start-array '[' characters + are encontered (see the GetStart() function). + This means that the JSON input text may contain anything + before the first start-object/array character except these two chars themselves + unless they are included in a C/C++ comment. + Comment lines that apear before the first start array/object character, + are non ignored if the parser is constructed with the wxJSONREADER_STORE_COMMENT + flag: they are added to the comment's array of the root JSON value. + + Note that the parsing process stops when the internal DoRead() function + returns. Because that function is recursive, the top-level close-object + '}' or close-array ']' character cause the top-level DoRead() function + to return thus stopping the parsing process regardless the EOF condition. + This means that the JSON input text may contain anything \b after + the top-level close-object/array character. + Here are some examples: + + Returns a wxJSONTYPE_INVALID value (invalid JSON value) + \code + // this text does not contain an open array/object character + \endcode + + Returns a wxJSONTYPE_OBJECT value of Size() = 0 + \code + { + } + \endcode + + Returns a wxJSONTYPE_ARRAY value of Size() = 0 + \code + [ + ] + \endcode + + Text before and after the top-level open/close characters is ignored. + \code + This non-JSON text does not cause the parser to report errors or warnings + { + } + This non-JSON text does not cause the parser to report errors or warnings + \endcode + + + \par Extensions + + The wxJSON parser recognizes all JSON text plus some extensions + that are not part of the JSON syntax but that many other JSON + implementations do recognize. + If the input text contains the following non-JSON text, the parser + reports the situation as \e warnings and not as \e errors unless + the parser object was constructed with the wxJSONREADER_STRICT + flag. In the latter case the wxJSON parser is not tolerant. + + \li C/C++ comments: the parser recognizes C and C++ comments. + Comments can optionally be stored in the value they refer + to and can also be written back to the JSON text document. + To know more about comment storage see \ref wxjson_comments + + \li case tolerance: JSON syntax states that the literals \c null, + \c true and \c false must be lowercase; the wxJSON parser + also recognizes mixed case literals such as, for example, + \b Null or \b FaLSe. A \e warning is emitted. + + \li wrong or missing closing character: wxJSON parser is tolerant + about the object / array closing character. When an open-array + character '[' is encontered, the parser expects the + corresponding close-array character ']'. If the character + encontered is a close-object char '}' a warning is reported. + A warning is also reported if the character is missing when + the end-of-file is reached. + + \li multi-line strings: this feature allows a JSON string type to be + splitted in two or more lines as in the standard C/C++ + languages. The drawback is that this feature is error-prone + and you have to use it with care. + For more info about this topic read \ref wxjson_tutorial_style_split + + Note that you can control how much error-tolerant should the parser be + and also you can specify how many and what extensions are recognized. + See the constructor's parameters for more details. + + \par Unicode vs ANSI + + The parser can read JSON text from two very different kind of objects: + + \li a string object (\b wxString) + \li a stream object (\b wxInputStream) + + When the input is from a string object, the character represented in the + string is platform- and mode- dependant; in other words, characters are + represented differently: in ANSI builds they depend on the charset in use + and in Unicode builds they depend on the platform (UCS-2 on win32, UCS-4 + or UTF-8 on GNU/Linux). + + When the input is from a stream object, the only recognized encoding format + is UTF-8 for both ANSI and Unicode builds. + + \par Example: + + \code + wxJSONValue value; + wxJSONReader reader; + + // open a text file that contains the UTF-8 encoded JSON text + wxFFileInputStream jsonText( _T("filename.utf8"), _T("r")); + + // read the file + int numErrors = reader.Parse( jsonText, &value ); + + if ( numErrors > 0 ) { + ::MessageBox( _T("Error reading the input file")); + } + \endcode + + Starting from version 1.1.0 the wxJSON reader and the writer has changed in + their internal organization. + To know more about ANSI and Unicode mode read \ref wxjson_tutorial_unicode. +*/ + + + +// if you have the debug build of wxWidgets and wxJSON you can see +// trace messages by setting the: +// WXTRACE=traceReader StoreComment +// environment variable +static const wxChar* traceMask = _T("traceReader"); +static const wxChar* storeTraceMask = _T("StoreComment"); + + +//! Ctor +/*! + Construct a JSON parser object with the given parameters. + + JSON parser objects should always be constructed on the stack but + it does not hurt to have a global JSON parser. + + \param flags this paramter controls how much error-tolerant should the + parser be + + \param maxErrors the maximum number of errors (and warnings, too) that are + reported by the parser. When the number of errors reaches this limit, + the parser stops to read the JSON input text and no other error is + reported. + + The \c flag parameter is the combination of ZERO or more of the + following constants OR'ed toghether: + + \li wxJSONREADER_ALLOW_COMMENTS: C/C++ comments are recognized by the + parser; a warning is reported by the parser + \li wxJSONREADER_STORE_COMMENTS: C/C++ comments, if recognized, are + stored in the value they refer to and can be rewritten back to + the JSON text + \li wxJSONREADER_CASE: the parser recognizes mixed-case literal strings + \li wxJSONREADER_MISSING: the parser allows missing or wrong close-object + and close-array characters + \li wxJSONREADER_MULTISTRING: strings may be splitted in two or more + lines + \li wxJSONREADER_COMMENTS_AFTER: if STORE_COMMENTS if defined, the parser + assumes that comment lines apear \b before the value they + refer to unless this constant is specified. In the latter case, + comments apear \b after the value they refer to. + \li wxJSONREADER_NOUTF8_STREAM: suppress UTF-8 conversion when reading a + string value from a stream: the reader assumes that the input stream + is encoded in ANSI format and not in UTF-8; only meaningfull in ANSI + builds, this flag is simply ignored in Unicode builds. + + You can also use the following shortcuts to specify some predefined + flag's combinations: + + \li wxJSONREADER_STRICT: all wxJSON extensions are reported as errors, this + is the same as specifying a ZERO value as \c flags. + \li wxJSONREADER_TOLERANT: this is the same as ALLOW_COMMENTS | CASE | + MISSING | MULTISTRING; all wxJSON extensions are turned on but comments + are not stored in the value objects. + + \par Example: + + The following code fragment construct a JSON parser, turns on all + wxJSON extensions and also stores C/C++ comments in the value object + they refer to. The parser assumes that the comments apear before the + value: + + \code + wxJSONReader reader( wxJSONREADER_TOLERANT | wxJSONREADER_STORE_COMMENTS ); + wxJSONValue root; + int numErrors = reader.Parse( jsonText, &root ); + \endcode +*/ +wxJSONReader::wxJSONReader(int flags, int maxErrors) +{ + m_flags = flags; + m_maxErrors = maxErrors; + m_noUtf8 = false; +#if !defined( wxJSON_USE_UNICODE ) + // in ANSI builds we can suppress UTF-8 conversion for both the writer and the reader + if (m_flags & wxJSONREADER_NOUTF8_STREAM) { + m_noUtf8 = true; + } +#endif + +} + +//! Dtor - does nothing +wxJSONReader::~wxJSONReader() +{ +} + +//! Parse the JSON document. +/*! + The two overloaded versions of the \c Parse() function read a + JSON text stored in a wxString object or in a wxInputStream + object. + + If \c val is a NULL pointer, the function does not store the + values: it can be used as a JSON checker in order to check the + syntax of the document. + Returns the number of \b errors found in the document. + If the returned value is ZERO and the parser was constructed + with the \c wxJSONREADER_STRICT flag, then the parsed document + is \e well-formed and it only contains valid JSON text. + + If the \c wxJSONREADER_TOLERANT flag was used in the parser's + constructor, then a return value of ZERO + does not mean that the document is \e well-formed because it may + contain comments and other extensions that are not fatal for the + wxJSON parser but other parsers may fail to recognize. + You can use the \c GetWarningCount() function to know how many + wxJSON extensions are present in the JSON input text. + + Note that the JSON value object \c val is not cleared by this + function unless its type is of the wrong type. + In other words, if \c val is of type wxJSONTYPE_ARRAY and it already + contains 10 elements and the input document starts with a + '[' (open-array char) then the elements read from the document are + \b appended to the existing ones. + + On the other hand, if the text document starts with a '{' (open-object) char + then this function must change the type of the \c val object to + \c wxJSONTYPE_OBJECT and the old content of 10 array elements will be lost. + + \par Different input types + + The real parsing process in done using UTF-8 streams. If the input is + from a \b wxString object, the Parse function first converts the input string + in a temporary \b wxMemoryInputStream which contains the UTF-8 conversion + of the string itself. + Next, the overloaded Parse function is called. + + @param doc the JSON text that has to be parsed + @param val the wxJSONValue object that contains the parsed text; if NULL the + parser do not store anything but errors and warnings are reported + @return the total number of errors encontered +*/ +int +wxJSONReader::Parse(const wxString& doc, wxJSONValue* val) +{ +#if !defined( wxJSON_USE_UNICODE ) + // in ANSI builds input from a string never use UTF-8 conversion + bool noUtf8_bak = m_noUtf8; // save the current setting + m_noUtf8 = true; +#endif + + // convert the string to a UTF-8 / ANSI memory stream and calls overloaded Parse() + char* readBuff = 0; + wxCharBuffer utf8CB = doc.ToUTF8(); // the UTF-8 buffer +#if !defined( wxJSON_USE_UNICODE ) + wxCharBuffer ansiCB(doc.c_str()); // the ANSI buffer + if (m_noUtf8) { + readBuff = ansiCB.data(); + } + else { + readBuff = utf8CB.data(); + } +#else + readBuff = utf8CB.data(); +#endif + + // now construct the temporary memory input stream + size_t len = strlen(readBuff); + wxMemoryInputStream is(readBuff, len); + + int numErr = Parse(is, val); +#if !defined( wxJSON_USE_UNICODE ) + m_noUtf8 = noUtf8_bak; +#endif + return numErr; +} + +//! \overload Parse( const wxString&, wxJSONValue* ) +int +wxJSONReader::Parse(wxInputStream& is, wxJSONValue* val) +{ + // if val == 0 the 'temp' JSON value will be passed to DoRead() + wxJSONValue temp; + m_level = 0; + m_depth = 0; + m_lineNo = 1; + m_colNo = 1; + m_peekChar = -1; + m_errors.clear(); + m_warnings.clear(); + + // if a wxJSONValue is not passed to the Parse function + // we set the temparary object created on the stack + // I know this will slow down the validation of input + if (val == 0) { + val = &temp; + } + wxASSERT(val); + + // set the wxJSONValue object's pointers for comment storage + m_next = val; + m_next->SetLineNo(-1); + m_lastStored = 0; + m_current = 0; + + int ch = GetStart(is); + switch (ch) { + case '{': + val->SetType(wxJSONTYPE_OBJECT); + break; + case '[': + val->SetType(wxJSONTYPE_ARRAY); + break; + default: + AddError(_T("Cannot find a start object/array character")); + return m_errors.size(); + break; + } + + // returning from DoRead() could be for EOF or for + // the closing array-object character + // if -1 is returned, it is as an error because the lack + // of close-object/array characters + // note that the missing close-chars error messages are + // added by the DoRead() function + ch = DoRead(is, *val); + return m_errors.size(); +} + + +//! Returns the start of the document +/*! + This is the first function called by the Parse() function and it searches + the input stream for the starting character of a JSON text and returns it. + JSON text start with '{' or '['. + If the two starting characters are inside a C/C++ comment, they + are ignored. + Returns the JSON-text start character or -1 on EOF. + + @param is the input stream that contains the JSON text + @return -1 on errors or EOF; one of '{' or '[' +*/ +int +wxJSONReader::GetStart(wxInputStream& is) +{ + int ch = 0; + do { + switch (ch) { + case 0: + ch = ReadChar(is); + break; + case '{': + return ch; + break; + case '[': + return ch; + break; + case '/': + ch = SkipComment(is); + StoreComment(0); + break; + default: + ch = ReadChar(is); + break; + } + } while (ch >= 0); + return ch; +} + +//! Return a reference to the error message's array. +const wxArrayString& +wxJSONReader::GetErrors() const +{ + return m_errors; +} + +//! Return a reference to the warning message's array. +const wxArrayString& +wxJSONReader::GetWarnings() const +{ + return m_warnings; +} + +//! Return the depth of the JSON input text +/*! + The function returns the number of times the recursive \c DoRead function was + called in the parsing process thus returning the maximum depth of the JSON + input text. +*/ +int +wxJSONReader::GetDepth() const +{ + return m_depth; +} + + + +//! Return the size of the error message's array. +int +wxJSONReader::GetErrorCount() const +{ + return m_errors.size(); +} + +//! Return the size of the warning message's array. +int +wxJSONReader::GetWarningCount() const +{ + return m_warnings.size(); +} + + +//! Read a character from the input JSON document. +/*! + The function returns the next byte from the UTF-8 stream as an INT. + In case of errors or EOF, the function returns -1. + The function also updates the \c m_lineNo and \c m_colNo data + members and converts all CR+LF sequence in LF. + + This function only returns one byte UTF-8 (one code unit) + at a time and not Unicode code points. + The only reason for this function is to process line and column + numbers. + + @param is the input stream that contains the JSON text + @return the next char (one single byte) in the input stream or -1 on error or EOF +*/ +int +wxJSONReader::ReadChar(wxInputStream& is) +{ + if (is.Eof()) { + return -1; + } + + unsigned char ch = is.GetC(); + size_t last = is.LastRead(); // returns ZERO if EOF + if (last == 0) { + return -1; + } + + // the function also converts CR in LF. only LF is returned + // in the case of CR+LF + int nextChar; + + if (ch == '\r') { + m_colNo = 1; + nextChar = PeekChar(is); + if (nextChar == -1) { + return -1; + } + else if (nextChar == '\n') { + ch = is.GetC(); + } + } + if (ch == '\n') { + ++m_lineNo; + m_colNo = 1; + } + else { + ++m_colNo; + } + return (int)ch; +} + + +//! Peek a character from the input JSON document +/*! + This function just calls the \b Peek() function on the stream + and returns it. + + @param is the input stream that contains the JSON text + @return the next char (one single byte) in the input stream or -1 on error or EOF +*/ +int +wxJSONReader::PeekChar(wxInputStream& is) +{ + int ch = -1; unsigned char c; + if (!is.Eof()) { + c = is.Peek(); + ch = c; + } + return ch; +} + + +//! Reads the JSON text document (internal use) +/*! + This is a recursive function that is called by \c Parse() + and by the \c DoRead() function itself when a new object / + array character is encontered. + The function returns when a EOF condition is encontered or + when the corresponding close-object / close-array char is encontered. + The function also increments the \c m_level + data member when it is entered and decrements it on return. + It also sets \c m_depth equal to \c m_level if \c m_depth is + less than \c m_level. + + The function is the heart of the wxJSON parser class but it is + also very easy to understand because JSON syntax is very + easy. + + Returns the last close-object/array character read or -1 on EOF. + + @param is the input stream that contains the JSON text + @param parent the JSON value object that is the parent of all subobjects + read by the function until the next close-object/array (for + the top-level \c DoRead function \c parent is the root JSON object) + @return one of close-array or close-object char or -1 on error or EOF +*/ +int +wxJSONReader::DoRead(wxInputStream& is, wxJSONValue& parent) +{ + ++m_level; + if (m_depth < m_level) { + m_depth = m_level; + } + + // 'value' is the wxJSONValue structure that has to be + // read. Data read from the JSON text input is stored + // in the following object. + wxJSONValue value(wxJSONTYPE_INVALID); + + // sets the pointers to the current, next and last-stored objects + // in order to determine the value to which a comment refers to + m_next = &value; + m_current = &parent; + m_current->SetLineNo(m_lineNo); + m_lastStored = 0; + + // the 'key' string is stored from 'value' when a ':' is encontered + wxString key; + + // the character read: -1=EOF, 0=to be read + int ch = 0; + + do { // we read until ch < 0 + switch (ch) { + case 0: + ch = ReadChar(is); + break; + case ' ': + case '\t': + case '\n': + case '\r': + ch = SkipWhiteSpace(is); + break; + case -1: // the EOF + break; + case '/': + ch = SkipComment(is); + StoreComment(&parent); + break; + + case '{': + if (parent.IsObject()) { + if (key.empty()) { + AddError(_T("\'{\' is not allowed here (\'name\' is missing")); + } + if (value.IsValid()) { + AddError(_T("\'{\' cannot follow a \'value\'")); + } + } + else if (parent.IsArray()) { + if (value.IsValid()) { + AddError(_T("\'{\' cannot follow a \'value\' in JSON array")); + } + } + else { + wxJSON_ASSERT(0); // always fails + } + + // the openobject char cause the DoRead() to be called recursively + value.SetType(wxJSONTYPE_OBJECT); + ch = DoRead(is, value); + break; + + case '}': + if (!parent.IsObject()) { + AddWarning(wxJSONREADER_MISSING, + _T("Trying to close an array using the \'}\' (close-object) char")); + } + // close-object: store the current value, if any + StoreValue(ch, key, value, parent); + m_current = &parent; + m_next = 0; + m_current->SetLineNo(m_lineNo); + ch = ReadChar(is); + return ch; + break; + + case '[': + if (parent.IsObject()) { + if (key.empty()) { + AddError(_T("\'[\' is not allowed here (\'name\' is missing")); + } + if (value.IsValid()) { + AddError(_T("\'[\' cannot follow a \'value\' text")); + } + } + else if (parent.IsArray()) { + if (value.IsValid()) { + AddError(_T("\'[\' cannot follow a \'value\'")); + } + } + else { + wxJSON_ASSERT(0); // always fails + } + // open-array cause the DoRead() to be called recursively + value.SetType(wxJSONTYPE_ARRAY); + ch = DoRead(is, value); + break; + + case ']': + if (!parent.IsArray()) { + // wrong close-array char (should be close-object) + AddWarning(wxJSONREADER_MISSING, + _T("Trying to close an object using the \']\' (close-array) char")); + } + StoreValue(ch, key, value, parent); + m_current = &parent; + m_next = 0; + m_current->SetLineNo(m_lineNo); + return 0; // returning ZERO for reading the next char + break; + + case ',': + // store the value, if any + StoreValue(ch, key, value, parent); + key.clear(); + ch = ReadChar(is); + break; + + case '\"': + ch = ReadString(is, value); // read a JSON string type + m_current = &value; + m_next = 0; + break; + + case '\'': + ch = ReadMemoryBuff(is, value); // read a memory buffer type + m_current = &value; + m_next = 0; + break; + + case ':': // key / value separator + m_current = &value; + m_current->SetLineNo(m_lineNo); + m_next = 0; + if (!parent.IsObject()) { + AddError(_T("\':\' can only used in object's values")); + } + else if (!value.IsString()) { + AddError(_T("\':\' follows a value which is not of type \'string\'")); + } + else if (!key.empty()) { + AddError(_T("\':\' not allowed where a \'name\' string was already available")); + } + else { + // the string in 'value' is set as the 'key' + key = value.AsString(); + value.SetType(wxJSONTYPE_INVALID); + } + ch = ReadChar(is); + break; + + default: + // no special char: it is a literal or a number + // errors are checked in the 'ReadValue()' function. + m_current = &value; + m_current->SetLineNo(m_lineNo); + m_next = 0; + ch = ReadValue(is, ch, value); + break; + } // end switch + } while (ch >= 0); + + // the DoRead() should return when the close-object/array char is encontered + // if we are here, the EOF condition was encontered so one or more close-something + // characters are missing + if (parent.IsArray()) { + AddWarning(wxJSONREADER_MISSING, _T("\']\' missing at end of file")); + } + else if (parent.IsObject()) { + AddWarning(wxJSONREADER_MISSING, _T("\'}\' missing at end of file")); + } + else { + wxJSON_ASSERT(0); + } + + // we store the value, as there is a missing close-object/array char + StoreValue(ch, key, value, parent); + + --m_level; + return ch; +} + +//! Store a value in the parent object. +/*! + The function is called by \c DoRead() when a the comma + or a close-object/array character is encontered and stores the current + value read by the parser in the parent object. + The function checks that \c value is not invalid and that \c key is + not an empty string if \c parent is an object. + + \param ch the character read: a comma or close objecty/array char + \param key the \b key string: must be empty if \c parent is an array + \param value the current JSON value to be stored in \c parent + \param parent the JSON value that is the parent of \c value. + \return none +*/ +void +wxJSONReader::StoreValue(int ch, const wxString& key, wxJSONValue& value, wxJSONValue& parent) +{ + // if 'ch' == } or ] than value AND key may be empty when a open object/array + // is immediatly followed by a close object/array + // + // if 'ch' == , (comma) value AND key (for TypeMap) cannot be empty + // + wxLogTrace(traceMask, _T("(%s) ch=%d char=%c"), __PRETTY_FUNCTION__, ch, (char)ch); + wxLogTrace(traceMask, _T("(%s) value=%s"), __PRETTY_FUNCTION__, value.AsString().c_str()); + + m_current = 0; + m_next = &value; + m_lastStored = 0; + m_next->SetLineNo(-1); + + if (!value.IsValid() && key.empty()) { + // OK, if the char read is a close-object or close-array + if (ch == '}' || ch == ']') { + m_lastStored = 0; + wxLogTrace(traceMask, _T("(%s) key and value are empty, returning"), + __PRETTY_FUNCTION__); + } + else { + AddError(_T("key or value is missing for JSON value")); + } + } + else { + // key or value are not empty + if (parent.IsObject()) { + if (!value.IsValid()) { + AddError(_T("cannot store the value: \'value\' is missing for JSON object type")); + } + else if (key.empty()) { + AddError(_T("cannot store the value: \'key\' is missing for JSON object type")); + } + else { + // OK, adding the value to parent key/value map + wxLogTrace(traceMask, _T("(%s) adding value to key:%s"), + __PRETTY_FUNCTION__, key.c_str()); + parent[key] = value; + m_lastStored = &(parent[key]); + m_lastStored->SetLineNo(m_lineNo); + } + } + else if (parent.IsArray()) { + if (!value.IsValid()) { + AddError(_T("cannot store the item: \'value\' is missing for JSON array type")); + } + if (!key.empty()) { + AddError(_T("cannot store the item: \'key\' (\'%s\') is not permitted in JSON array type"), key); + } + wxLogTrace(traceMask, _T("(%s) appending value to parent array"), + __PRETTY_FUNCTION__); + parent.Append(value); + const wxJSONInternalArray* arr = parent.AsArray(); + wxJSON_ASSERT(arr); + m_lastStored = &(arr->Last()); + m_lastStored->SetLineNo(m_lineNo); + } + else { + wxJSON_ASSERT(0); // should never happen + } + } + value.SetType(wxJSONTYPE_INVALID); + value.ClearComments(); +} + +//! Add a error message to the error's array +/*! + The overloaded versions of this function add an error message to the + error's array stored in \c m_errors. + The error message is formatted as follows: + + \code + Error: line xxx, col xxx - + \endcode + + The \c msg parameter is the description of the error; line's and column's + number are automatically added by the functions. + The \c fmt parameter is a format string that has the same syntax as the \b printf + function. + Note that it is the user's responsability to provide a format string suitable + with the arguments: another string or a character. +*/ +void +wxJSONReader::AddError(const wxString& msg) +{ + wxString err; + err.Printf(_T("Error: line %d, col %d - %s"), m_lineNo, m_colNo, msg.c_str()); + + wxLogTrace(traceMask, _T("(%s) %s"), __PRETTY_FUNCTION__, err.c_str()); + + if ((int)m_errors.size() < m_maxErrors) { + m_errors.Add(err); + } + else if ((int)m_errors.size() == m_maxErrors) { + m_errors.Add(_T("ERROR: too many error messages - ignoring further errors")); + } + // else if ( m_errors > m_maxErrors ) do nothing, thus ignore the error message +} + +//! \overload AddError( const wxString& ) +void +wxJSONReader::AddError(const wxString& fmt, const wxString& str) +{ + wxString s; + s.Printf(fmt.c_str(), str.c_str()); + AddError(s); +} + +//! \overload AddError( const wxString& ) +void +wxJSONReader::AddError(const wxString& fmt, wxChar c) +{ + wxString s; + s.Printf(fmt.c_str(), c); + AddError(s); +} + +//! Add a warning message to the warning's array +/*! + The warning description is as follows: + \code + Warning: line xxx, col xxx - + \endcode + + Warning messages are generated by the parser when the JSON + text that has been read is not well-formed but the + error is not fatal and the parser recognizes the text + as an extension to the JSON standard (see the parser's ctor + for more info about wxJSON extensions). + + Note that the parser has to be constructed with a flag that + indicates if each individual wxJSON extension is on. + If the warning message is related to an extension that is not + enabled in the parser's \c m_flag data member, this function + calls AddError() and the warning message becomes an error + message. + The \c type parameter is one of the same constants that + specify the parser's extensions. + If type is ZERO than the function always adds a warning +*/ +void +wxJSONReader::AddWarning(int type, const wxString& msg) +{ + // if 'type' AND 'm_flags' == 1 than the extension is + // ON. Otherwise it is OFF anf the function calls AddError() + if (type != 0) { + if ((type & m_flags) == 0) { + AddError(msg); + return; + } + } + + wxString err; + err.Printf(_T("Warning: line %d, col %d - %s"), m_lineNo, m_colNo, msg.c_str()); + + wxLogTrace(traceMask, _T("(%s) %s"), __PRETTY_FUNCTION__, err.c_str()); + if ((int)m_warnings.size() < m_maxErrors) { + m_warnings.Add(err); + } + else if ((int)m_warnings.size() == m_maxErrors) { + m_warnings.Add(_T("Error: too many warning messages - ignoring further warnings")); + } + // else do nothing, thus ignore the warning message +} + +//! Skip all whitespaces. +/*! + The function reads characters from the input text + and returns the first non-whitespace character read or -1 + if EOF. + Note that the function does not rely on the \b isspace function + of the C library but checks the space constants: space, TAB and + LF. +*/ +int +wxJSONReader::SkipWhiteSpace(wxInputStream& is) +{ + // just read one byte at a time and check for whitespaces + int ch; + do { + ch = ReadChar(is); + if (ch < 0) { + break; + } + } while (ch == ' ' || ch == '\n' || ch == '\t'); + wxLogTrace(traceMask, _T("(%s) end whitespaces line=%d col=%d"), + __PRETTY_FUNCTION__, m_lineNo, m_colNo); + return ch; +} + +//! Skip a comment +/*! + The function is called by DoRead() when a '/' (slash) character + is read from the input stream assuming that a C/C++ comment is starting. + Returns the first character that follows the comment or + -1 on EOF. + The function also adds a warning message because comments are not + valid JSON text. + The function also stores the comment, if any, in the \c m_comment data + member: it can be used by the DoRead() function if comments have to be + stored in the value they refer to. +*/ +int +wxJSONReader::SkipComment(wxInputStream& is) +{ + static const wxChar* warn = + _T("Comments may be tolerated in JSON text but they are not part of JSON syntax"); + + // if it is a comment, then a warning is added to the array + // otherwise it is an error: values cannot start with a '/' + // read the char next to the first slash + int ch = ReadChar(is); + if (ch < 0) { + return -1; + } + + wxLogTrace(storeTraceMask, _T("(%s) start comment line=%d col=%d"), + __PRETTY_FUNCTION__, m_lineNo, m_colNo); + + // the temporary UTF-8/ANSI buffer that holds the comment string. This will be + // converted to a wxString object using wxString::FromUTF8() or From8BitData() + wxMemoryBuffer utf8Buff; + unsigned char c; + + if (ch == '/') { // C++ comment, read until end-of-line + // C++ comment strings are in UTF-8 format. we store all + // UTF-8 code units until the first LF or CR+LF + AddWarning(wxJSONREADER_ALLOW_COMMENTS, warn); + m_commentLine = m_lineNo; + utf8Buff.AppendData("//", 2); + + while (ch >= 0) { + if (ch == '\n') { + break; + } + if (ch == '\r') { + ch = PeekChar(is); + if (ch == '\n') { + ch = ReadChar(is); + } + break; + } + else { + // store the char in the UTF8 temporary buffer + c = (unsigned char)ch; + utf8Buff.AppendByte(c); + } + ch = ReadChar(is); + } + // now convert the temporary UTF-8 buffer + m_comment = wxString::FromUTF8((const char*)utf8Buff.GetData(), + utf8Buff.GetDataLen()); + } + + // check if a C-style comment + else if (ch == '*') { // C-style comment + AddWarning(wxJSONREADER_ALLOW_COMMENTS, warn); + m_commentLine = m_lineNo; + utf8Buff.AppendData("/*", 2); + while (ch >= 0) { + // check the END-COMMENT chars ('*/') + if (ch == '*') { + ch = PeekChar(is); + if (ch == '/') { + ch = ReadChar(is); // read the '/' char + ch = ReadChar(is); // read the next char that will be returned + utf8Buff.AppendData("*/", 2); + break; + } + } + // store the char in the UTF8 temporary buffer + c = (unsigned char)ch; + utf8Buff.AppendByte(c); + ch = ReadChar(is); + } + // now convert the temporary buffer in a wxString object + if (m_noUtf8) { + m_comment = wxString::From8BitData((const char*)utf8Buff.GetData(), + utf8Buff.GetDataLen()); + } + else { + m_comment = wxString::FromUTF8((const char*)utf8Buff.GetData(), + utf8Buff.GetDataLen()); + } + } + + else { // it is not a comment, return the character next the first '/' + AddError(_T("Strange '/' (did you want to insert a comment?)")); + // we read until end-of-line OR end of C-style comment OR EOF + // because a '/' should be a start comment + while (ch >= 0) { + ch = ReadChar(is); + if (ch == '*' && PeekChar(is) == '/') { + break; + } + if (ch == '\n') { + break; + } + } + // read the next char that will be returned + ch = ReadChar(is); + } + wxLogTrace(traceMask, _T("(%s) end comment line=%d col=%d"), + __PRETTY_FUNCTION__, m_lineNo, m_colNo); + wxLogTrace(storeTraceMask, _T("(%s) end comment line=%d col=%d"), + __PRETTY_FUNCTION__, m_lineNo, m_colNo); + wxLogTrace(storeTraceMask, _T("(%s) comment=%s"), + __PRETTY_FUNCTION__, m_comment.c_str()); + return ch; +} + +//! Read a string value +/*! + The function reads a string value from input stream and it is + called by the \c DoRead() function when it enconters the + double quote characters. + The function read all bytes up to the next double quotes + (unless it is escaped) and stores them in a temporary UTF-8 + memory buffer. + Also, the function processes the escaped characters defined + in the JSON syntax. + + Next, the function tries to convert the UTF-8 buffer to a + \b wxString object using the \b wxString::FromUTF8 function. + Depending on the build mode, we can have the following: + \li in Unicode the function always succeeds, provided that the + buffer contains valid UTF-8 code units. + + \li in ANSI builds the conversion may fail because of the presence of + unrepresentable characters in the current locale. In this case, + the default behaviour is to perform a char-by-char conversion; every + char that cannot be represented in the current locale is stored as + \e unicode \e escaped \e sequence + + \li in ANSI builds, if the reader is constructed with the wxJSONREADER_NOUTF8_STREAM + then no conversion takes place and the UTF-8 temporary buffer is simply + \b copied to the \b wxString object + + The string is, finally, stored in the provided wxJSONValue argument + provided that it is empty or it contains a string value. + This is because the parser class recognizes multi-line strings + like the following one: + \code + [ + "This is a very long string value which is splitted into more" + "than one line because it is more human readable" + ] + \endcode + Because of the lack of the value separator (,) the parser + assumes that the string was splitted into several double-quoted + strings. + If the value does not contain a string then an error is + reported. + Splitted strings cause the parser to report a warning. +*/ +int +wxJSONReader::ReadString(wxInputStream& is, wxJSONValue& val) +{ + // the char last read is the opening qoutes (") + + wxMemoryBuffer utf8Buff; + char ues[8]; // stores a Unicode Escaped Esquence: \uXXXX + + int ch = 0; + while (ch >= 0) { + ch = ReadChar(is); + unsigned char c = (unsigned char)ch; + if (ch == '\\') { // an escape sequence + ch = ReadChar(is); + switch (ch) { + case -1: // EOF + break; + case 't': + utf8Buff.AppendByte('\t'); + break; + case 'n': + utf8Buff.AppendByte('\n'); + break; + case 'b': + utf8Buff.AppendByte('\b'); + break; + case 'r': + utf8Buff.AppendByte('\r'); + break; + case '\"': + utf8Buff.AppendByte('\"'); + break; + case '\\': + utf8Buff.AppendByte('\\'); + break; + case '/': + utf8Buff.AppendByte('/'); + break; + case 'f': + utf8Buff.AppendByte('\f'); + break; + case 'u': + ch = ReadUES(is, ues); + if (ch < 0) { // if EOF, returns + return ch; + } + // append the escaped character to the UTF8 buffer + AppendUES(utf8Buff, ues); + // many thanks to Bryan Ashby who discovered this bug + continue; + // break; + default: + AddError(_T("Unknow escaped character \'\\%c\'"), ch); + } + } + else { + // we have read a non-escaped character so we have to append it to + // the temporary UTF-8 buffer until the next quote char + if (ch == '\"') { + break; + } + utf8Buff.AppendByte(c); + } + } + + // if UTF-8 conversion is disabled (ANSI builds only) we just copy the + // bit data to a wxString object + wxString s; + if (m_noUtf8) { + s = wxString::From8BitData((const char*)utf8Buff.GetData(), utf8Buff.GetDataLen()); + } + else { + // perform UTF-8 conversion + // first we check that the UTF-8 buffer is correct, i.e. it contains valid + // UTF-8 code points. + // this works in both ANSI and Unicode builds. + size_t convLen = wxConvUTF8.ToWChar(0, // wchar_t destination + 0, // size_t destLenght + (const char*)utf8Buff.GetData(), // char_t source + utf8Buff.GetDataLen()); // size_t sourceLenght + + if (convLen == wxCONV_FAILED) { + AddError(_T("String value: the UTF-8 stream is invalid")); + s.append(_T("")); + } + else { +#if defined( wxJSON_USE_UNICODE ) + // in Unicode just convert to wxString + s = wxString::FromUTF8((const char*)utf8Buff.GetData(), utf8Buff.GetDataLen()); +#else + // in ANSI, the conversion may fail and an empty string is returned + // in this case, the reader do a char-by-char conversion storing + // unicode escaped sequences of unrepresentable characters + s = wxString::FromUTF8((const char*)utf8Buff.GetData(), utf8Buff.GetDataLen()); + if (s.IsEmpty()) { + int r = ConvertCharByChar(s, utf8Buff); // return number of escaped sequences + if (r > 0) { + AddWarning(0, _T("The string value contains unrepresentable Unicode characters")); + } + } +#endif + } + } + wxLogTrace(traceMask, _T("(%s) line=%d col=%d"), + __PRETTY_FUNCTION__, m_lineNo, m_colNo); + wxLogTrace(traceMask, _T("(%s) string read=%s"), + __PRETTY_FUNCTION__, s.c_str()); + wxLogTrace(traceMask, _T("(%s) value=%s"), + __PRETTY_FUNCTION__, val.AsString().c_str()); + + // now assign the string to the JSON-value 'value' + // must check that: + // 'value' is empty + // 'value' is a string; concatenate it but emit warning + if (!val.IsValid()) { + wxLogTrace(traceMask, _T("(%s) assigning the string to value"), __PRETTY_FUNCTION__); + val = s; + } + else if (val.IsString()) { + AddWarning(wxJSONREADER_MULTISTRING, + _T("Multiline strings are not allowed by JSON syntax")); + wxLogTrace(traceMask, _T("(%s) concatenate the string to value"), __PRETTY_FUNCTION__); + val.Cat(s); + } + else { + AddError(_T("String value \'%s\' cannot follow another value"), s); + } + + // store the input text's line number when the string was stored in 'val' + val.SetLineNo(m_lineNo); + + // read the next char after the closing quotes and returns it + if (ch >= 0) { + ch = ReadChar(is); + } + return ch; +} + +//! Reads a token string +/*! + This function is called by the ReadValue() when the + first character encontered is not a special char + and it is not a double-quote. + The only possible type is a literal or a number which + all lies in the US-ASCII charset so their UTF-8 encodeing + is the same as US-ASCII. + The function simply reads one byte at a time from the stream + and appends them to a \b wxString object. + Returns the next character read. + + A token cannot include \e unicode \e escaped \e sequences + so this function does not try to interpret such sequences. + + @param is the input stream + @param ch the character read by DoRead + @param s the string object that contains the token read + @return -1 in case of errors or EOF +*/ +int +wxJSONReader::ReadToken(wxInputStream& is, int ch, wxString& s) +{ + int nextCh = ch; + while (nextCh >= 0) { + switch (nextCh) { + case ' ': + case ',': + case ':': + case '[': + case ']': + case '{': + case '}': + case '\t': + case '\n': + case '\r': + case '\b': + wxLogTrace(traceMask, _T("(%s) line=%d col=%d"), + __PRETTY_FUNCTION__, m_lineNo, m_colNo); + wxLogTrace(traceMask, _T("(%s) token read=%s"), + __PRETTY_FUNCTION__, s.c_str()); + return nextCh; + break; + default: + s.Append((unsigned char)nextCh, 1); + break; + } + // read the next character + nextCh = ReadChar(is); + } + wxLogTrace(traceMask, _T("(%s) EOF on line=%d col=%d"), + __PRETTY_FUNCTION__, m_lineNo, m_colNo); + wxLogTrace(traceMask, _T("(%s) EOF - token read=%s"), + __PRETTY_FUNCTION__, s.c_str()); + return nextCh; +} + +//! Read a value from input stream +/*! + The function is called by DoRead() when it enconters a char that is + not a special char nor a double-quote. + It assumes that the string is a numeric value or a literal + boolean value and stores it in the wxJSONValue object \c val. + + The function also checks that \c val is of type wxJSONTYPE_INVALID otherwise + an error is reported becasue a value cannot follow another value: + maybe a (,) or (:) is missing. + + If the literal starts with a digit, a plus or minus sign, the function + tries to interpret it as a number. The following are tried by the function, + in this order: + + \li if the literal starts with a digit: signed integer, then unsigned integer + and finally double conversion is tried + \li if the literal starts with a minus sign: signed integer, then double + conversion is tried + \li if the literal starts with plus sign: unsigned integer + then double conversion is tried + + Returns the next character or -1 on EOF. +*/ +int +wxJSONReader::ReadValue(wxInputStream& is, int ch, wxJSONValue& val) +{ + wxString s; + int nextCh = ReadToken(is, ch, s); + wxLogTrace(traceMask, _T("(%s) value=%s"), + __PRETTY_FUNCTION__, val.AsString().c_str()); + + if (val.IsValid()) { + AddError(_T("Value \'%s\' cannot follow a value: \',\' or \':\' missing?"), s); + return nextCh; + } + + // variables used for converting numeric values + bool r; double d; +#if defined( wxJSON_64BIT_INT ) + wxInt64 i64; + wxUint64 ui64; +#else + unsigned long int ul; long int l; +#endif + + // first try the literal strings lowercase and nocase + if (s == _T("null")) { + val.SetType(wxJSONTYPE_NULL); + wxLogTrace(traceMask, _T("(%s) value = NULL"), __PRETTY_FUNCTION__); + return nextCh; + } + else if (s.CmpNoCase(_T("null")) == 0) { + wxLogTrace(traceMask, _T("(%s) value = NULL"), __PRETTY_FUNCTION__); + AddWarning(wxJSONREADER_CASE, _T("the \'null\' literal must be lowercase")); + val.SetType(wxJSONTYPE_NULL); + return nextCh; + } + else if (s == _T("true")) { + wxLogTrace(traceMask, _T("(%s) value = TRUE"), __PRETTY_FUNCTION__); + val = true; + return nextCh; + } + else if (s.CmpNoCase(_T("true")) == 0) { + wxLogTrace(traceMask, _T("(%s) value = TRUE"), __PRETTY_FUNCTION__); + AddWarning(wxJSONREADER_CASE, _T("the \'true\' literal must be lowercase")); + val = true; + return nextCh; + } + else if (s == _T("false")) { + wxLogTrace(traceMask, _T("(%s) value = FALSE"), __PRETTY_FUNCTION__); + val = false; + return nextCh; + } + else if (s.CmpNoCase(_T("false")) == 0) { + wxLogTrace(traceMask, _T("(%s) value = FALSE"), __PRETTY_FUNCTION__); + AddWarning(wxJSONREADER_CASE, _T("the \'false\' literal must be lowercase")); + val = false; + return nextCh; + } + + + // try to convert to a number if the token starts with a digit, a plus or a minus + // sign. The function first states what type of conversion are tested: + // 1. first signed integer (not if 'ch' == '+') + // 2. unsigned integer (not if 'ch' == '-') + // 3. finally double + bool tSigned = true, tUnsigned = true, tDouble = true; + switch (ch) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + // first try a signed integer, then a unsigned integer, then a double + break; + + case '+': + // the plus sign forces a unsigned integer + tSigned = false; + break; + + case '-': + // try signed and double + tUnsigned = false; + break; + default: + AddError(_T("Literal \'%s\' is incorrect (did you forget quotes?)"), s); + return nextCh; + } + + if (tSigned) { +#if defined( wxJSON_64BIT_INT) + r = Strtoll(s, &i64); + wxLogTrace(traceMask, _T("(%s) convert to wxInt64 result=%d"), + __PRETTY_FUNCTION__, r); + if (r) { + // store the value + val = i64; + return nextCh; + } +#else + r = s.ToLong(&l); + wxLogTrace(traceMask, _T("(%s) convert to int result=%d"), + __PRETTY_FUNCTION__, r); + if (r) { + // store the value + val = (int)l; + return nextCh; + } +#endif + } + + if (tUnsigned) { +#if defined( wxJSON_64BIT_INT) + r = Strtoull(s, &ui64); + wxLogTrace(traceMask, _T("(%s) convert to wxUint64 result=%d"), + __PRETTY_FUNCTION__, r); + if (r) { + // store the value + val = ui64; + return nextCh; + } +#else + r = s.ToULong(&ul); + wxLogTrace(traceMask, _T("(%s) convert to int result=%d"), + __PRETTY_FUNCTION__, r); + if (r) { + // store the value + val = (unsigned int)ul; + return nextCh; + } +#endif + } + + if (tDouble) { + r = s.ToDouble(&d); + wxLogTrace(traceMask, _T("(%s) convert to double result=%d"), + __PRETTY_FUNCTION__, r); + if (r) { + // store the value + val = d; + return nextCh; + } + } + + + // the value is not syntactically correct + AddError(_T("Literal \'%s\' is incorrect (did you forget quotes?)"), s); + return nextCh; +} + + +//! Read a 4-hex-digit unicode character. +/*! + The function is called by ReadString() when the \b \\u sequence is + encontered; the sequence introduces a control character in the form: + \code + \uXXXX + \endcode + where XXXX is a four-digit hex code.. + The function reads four chars from the input UTF8 stream by calling ReadChar() + four times: if EOF is encontered before reading four chars, -1 is + also returned and no sequence interpretation is performed. + The function stores the 4 hexadecimal digits in the \c uesBuffer parameter. + + Returns the character after the hex sequence or -1 if EOF. + + \b NOTICE: although the JSON syntax states that only control characters + are represented in this way, the wxJSON library reads and recognizes all + unicode characters in the BMP. +*/ +int +wxJSONReader::ReadUES(wxInputStream& is, char* uesBuffer) +{ + int ch; + for (int i = 0; i < 4; i++) { + ch = ReadChar(is); + if (ch < 0) { + return ch; + } + uesBuffer[i] = (unsigned char)ch; + } + uesBuffer[4] = 0; // makes a ASCIIZ string + + return 0; +} + + +//! The function appends a Unice Escaped Sequence to the temporary UTF8 buffer +/*! + This function is called by \c ReadString() when a \e unicode \e escaped + \e sequence is read from the input text as for example: + + \code + \u0001 + \endcode + + which represents a control character. + The \c uesBuffer parameter contains the 4 hexadecimal digits that are + read from \c ReadUES. + + The function tries to convert the 4 hex digits in a \b wchar_t character + which is appended to the memory buffer \c utf8Buff after converting it + to UTF-8. + + If the conversion from hexadecimal fails, the function does not + store the character in the UTF-8 buffer and an error is reported. + The function is the same in ANSI and Unicode. + Returns -1 if the buffer does not contain valid hex digits. + sequence. On success returns ZERO. + + @param utf8Buff the UTF-8 buffer to which the control char is written + @param uesBuffer the four-hex-digits read from the input text + @return ZERO on success, -1 if the four-hex-digit buffer cannot be converted +*/ +int +wxJSONReader::AppendUES(wxMemoryBuffer& utf8Buff, const char* uesBuffer) +{ + unsigned long l; + int r = sscanf(uesBuffer, "%lx", &l); // r is the assigned items + if (r != 1) { + AddError(_T("Invalid Unicode Escaped Sequence")); + return -1; + } + wxLogTrace(traceMask, _T("(%s) unicode sequence=%s code=%ld"), + __PRETTY_FUNCTION__, uesBuffer, l); + + wchar_t ch = (wchar_t)l; + char buffer[16]; + size_t len = wxConvUTF8.FromWChar(buffer, 10, &ch, 1); + + // seems that the wxMBConv classes always appends a NULL byte to + // the converted buffer + if (len > 1) { + len = len - 1; + } + utf8Buff.AppendData(buffer, len); + + // sould never fail + wxASSERT(len != wxCONV_FAILED); + return 0; +} + +//! Store the comment string in the value it refers to. +/*! + The function searches a suitable value object for storing the + comment line that was read by the parser and temporarly + stored in \c m_comment. + The function searches the three values pointed to by: + \li \c m_next + \li \c m_current + \li \c m_lastStored + + The value that the comment refers to is: + + \li if the comment is on the same line as one of the values, the comment + refer to that value and it is stored as \b inline. + \li otherwise, if the comment flag is wxJSONREADER_COMMENTS_BEFORE, the comment lines + are stored in the value pointed to by \c m_next + \li otherwise, if the comment flag is wxJSONREADER_COMMENTS_AFTER, the comment lines + are stored in the value pointed to by \c m_current or m_latStored + + Note that the comment line is only stored if the wxJSONREADER_STORE_COMMENTS + flag was used when the parser object was constructed; otherwise, the + function does nothing and immediatly returns. + Also note that if the comment line has to be stored but the + function cannot find a suitable value to add the comment line to, + an error is reported (note: not a warning but an error). +*/ +void +wxJSONReader::StoreComment(const wxJSONValue* parent) +{ + wxLogTrace(storeTraceMask, _T("(%s) m_comment=%s"), __PRETTY_FUNCTION__, m_comment.c_str()); + wxLogTrace(storeTraceMask, _T("(%s) m_flags=%d m_commentLine=%d"), + __PRETTY_FUNCTION__, m_flags, m_commentLine); + wxLogTrace(storeTraceMask, _T("(%s) m_current=%p"), __PRETTY_FUNCTION__, m_current); + wxLogTrace(storeTraceMask, _T("(%s) m_next=%p"), __PRETTY_FUNCTION__, m_next); + wxLogTrace(storeTraceMask, _T("(%s) m_lastStored=%p"), __PRETTY_FUNCTION__, m_lastStored); + + // first check if the 'store comment' bit is on + if ((m_flags & wxJSONREADER_STORE_COMMENTS) == 0) { + m_comment.clear(); + return; + } + + // check if the comment is on the same line of one of the + // 'current', 'next' or 'lastStored' value + if (m_current != 0) { + wxLogTrace(storeTraceMask, _T("(%s) m_current->lineNo=%d"), + __PRETTY_FUNCTION__, m_current->GetLineNo()); + if (m_current->GetLineNo() == m_commentLine) { + wxLogTrace(storeTraceMask, _T("(%s) comment added to \'m_current\' INLINE"), + __PRETTY_FUNCTION__); + m_current->AddComment(m_comment, wxJSONVALUE_COMMENT_INLINE); + m_comment.clear(); + return; + } + } + if (m_next != 0) { + wxLogTrace(storeTraceMask, _T("(%s) m_next->lineNo=%d"), + __PRETTY_FUNCTION__, m_next->GetLineNo()); + if (m_next->GetLineNo() == m_commentLine) { + wxLogTrace(storeTraceMask, _T("(%s) comment added to \'m_next\' INLINE"), + __PRETTY_FUNCTION__); + m_next->AddComment(m_comment, wxJSONVALUE_COMMENT_INLINE); + m_comment.clear(); + return; + } + } + if (m_lastStored != 0) { + wxLogTrace(storeTraceMask, _T("(%s) m_lastStored->lineNo=%d"), + __PRETTY_FUNCTION__, m_lastStored->GetLineNo()); + if (m_lastStored->GetLineNo() == m_commentLine) { + wxLogTrace(storeTraceMask, _T("(%s) comment added to \'m_lastStored\' INLINE"), + __PRETTY_FUNCTION__); + m_lastStored->AddComment(m_comment, wxJSONVALUE_COMMENT_INLINE); + m_comment.clear(); + return; + } + } + + // if comment is BEFORE, store the comment in the 'm_next' + // or 'm_current' value + // if comment is AFTER, store the comment in the 'm_lastStored' + // or 'm_current' value + + if (m_flags & wxJSONREADER_COMMENTS_AFTER) { // comment AFTER + if (m_current) { + if (m_current == parent || !m_current->IsValid()) { + AddError(_T("Cannot find a value for storing the comment (flag AFTER)")); + } + else { + wxLogTrace(storeTraceMask, _T("(%s) comment added to m_current (AFTER)"), + __PRETTY_FUNCTION__); + m_current->AddComment(m_comment, wxJSONVALUE_COMMENT_AFTER); + } + } + else if (m_lastStored) { + wxLogTrace(storeTraceMask, _T("(%s) comment added to m_lastStored (AFTER)"), + __PRETTY_FUNCTION__); + m_lastStored->AddComment(m_comment, wxJSONVALUE_COMMENT_AFTER); + } + else { + wxLogTrace(storeTraceMask, + _T("(%s) cannot find a value for storing the AFTER comment"), __PRETTY_FUNCTION__); + AddError(_T("Cannot find a value for storing the comment (flag AFTER)")); + } + } + else { // comment BEFORE can only be added to the 'next' value + if (m_next) { + wxLogTrace(storeTraceMask, _T("(%s) comment added to m_next (BEFORE)"), + __PRETTY_FUNCTION__); + m_next->AddComment(m_comment, wxJSONVALUE_COMMENT_BEFORE); + } + else { + // cannot find a value for storing the comment + AddError(_T("Cannot find a value for storing the comment (flag BEFORE)")); + } + } + m_comment.clear(); +} + + +//! Return the number of bytes that make a character in stream input +/*! + This function returns the number of bytes that represent a unicode + code point in various encoding. + For example, if the input stream is UTF-32 the function returns 4. + Because the only recognized format for streams is UTF-8 the function + just calls UTF8NumBytes() and returns. + The function is, actually, not used at all. + +*/ +int +wxJSONReader::NumBytes(char ch) +{ + int n = UTF8NumBytes(ch); + return n; +} + +//! Compute the number of bytes that makes a UTF-8 encoded wide character. +/*! + The function counts the number of '1' bit in the character \c ch and + returns it. + The UTF-8 encoding specifies the number of bytes needed by a wide character + by coding it in the first byte. See below. + + Note that if the character does not contain a valid UTF-8 encoding + the function returns -1. + +\code + UCS-4 range (hex.) UTF-8 octet sequence (binary) + ------------------- ----------------------------- + 0000 0000-0000 007F 0xxxxxxx + 0000 0080-0000 07FF 110xxxxx 10xxxxxx + 0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx + 0001 0000-001F FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + 0020 0000-03FF FFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + 0400 0000-7FFF FFFF 1111110x 10xxxxxx ... 10xxxxxx +\endcode +*/ +int +wxJSONReader::UTF8NumBytes(char ch) +{ + int num = 0; // the counter of '1' bits + for (int i = 0; i < 8; i++) { + if ((ch & 0x80) == 0) { + break; + } + ++num; + ch = ch << 1; + } + + // note that if the char contains more than six '1' bits it is not + // a valid UTF-8 encoded character + if (num > 6) { + num = -1; + } + else if (num == 0) { + num = 1; + } + return num; +} + +//! Convert a UTF-8 memory buffer one char at a time +/*! + This function is used in ANSI mode when input from a stream is in UTF-8 + format and the UTF-8 buffer read cannot be converted to the locale + wxString object. + The function performs a char-by-char conversion of the buffer and appends + every representable character to the string \c s. + Characters that cannot be represented are stored as \e unicode \e escaped + \e sequences in the form: + \code + \uXXXX + \endcode + where XXXX is a for-hex-digits Unicode code point. + The function returns the number of characters that cannot be represented + in the current locale. +*/ +int +wxJSONReader::ConvertCharByChar(wxString& s, const wxMemoryBuffer& utf8Buffer) +{ + size_t len = utf8Buffer.GetDataLen(); + char* buff = (char*)utf8Buffer.GetData(); + char* buffEnd = buff + len; + + int result = 0; + char temp[16]; // the UTF-8 code-point + + while (buff < buffEnd) { + temp[0] = *buff; // the first UTF-8 code-unit + // compute the number of code-untis that make one UTF-8 code-point + int numBytes = NumBytes(*buff); + ++buff; + for (int i = 1; i < numBytes; i++) { + if (buff >= buffEnd) { + break; + } + temp[i] = *buff; // the first UTF-8 code-unit + ++buff; + } + //if ( buff >= buffEnd ) { + // break; + //} + // now convert 'temp' to a wide-character + wchar_t dst[10]; + size_t outLength = wxConvUTF8.ToWChar(dst, 10, temp, numBytes); + + // now convert the wide char to a locale dependent character + // len = wxConvLocal.FromWChar( temp, 16, dst, outLength ); + // len = wxConviso8859_1.FromWChar( temp, 16, dst, outLength ); + len = wxConvLibc.FromWChar(temp, 16, dst, outLength); + if (len == wxCONV_FAILED) { + ++result; + wxString t; + t.Printf(_T("\\u%04X"), (int)dst[0]); + s.Append(t); + } + else { + s.Append(temp[0], 1); + } + } // end while + return result; +} + +//! Read a memory buffer type +/*! + This function is called by DoRead() when the single-quote character is + encontered which starts a \e memory \e buffer type. + This type is a \b wxJSON extension so the function emits a warning + when such a type encontered. + If the reader is constructed without the \c wxJSONREADER_MEMORYBUFF flag + then the warning becomes an error. + To know more about this JSON syntax extension read \ref wxjson_tutorial_memorybuff + + @param is the input stream + @param val the JSON value that will hold the memory buffer value + @return the last char read or -1 in case of EOF +*/ + +union byte +{ + unsigned char c[2]; + short int b; +}; + +int +wxJSONReader::ReadMemoryBuff(wxInputStream& is, wxJSONValue& val) +{ + static const wxChar* membuffError = _T("the \'memory buffer\' type contains %d invalid digits"); + + AddWarning(wxJSONREADER_MEMORYBUFF, _T("the \'memory buffer\' type is not valid JSON text")); + + wxMemoryBuffer buff; + int ch = 0; int errors = 0; + unsigned char byte = 0; + while (ch >= 0) { + ch = ReadChar(is); + if (ch < 0) { + break; + } + if (ch == '\'') { + break; + } + // the conversion is done two chars at a time + unsigned char c1 = (unsigned char)ch; + ch = ReadChar(is); + if (ch < 0) { + break; + } + unsigned char c2 = (unsigned char)ch; + c1 -= '0'; + c2 -= '0'; + if (c1 > 9) { + c1 -= 7; + } + if (c2 > 9) { + c2 -= 7; + } + if (c1 > 15) { + ++errors; + } + else if (c2 > 15) { + ++errors; + } + else { + byte = (c1 * 16) + c2; + buff.AppendByte(byte); + } + } // end while + + if (errors > 0) { + wxString err; + err.Printf(membuffError, errors); + AddError(err); + } + + + // now assign the memory buffer object to the JSON-value 'value' + // must check that: + // 'value' is invalid OR + // 'value' is a memory buffer; concatenate it + if (!val.IsValid()) { + wxLogTrace(traceMask, _T("(%s) assigning the memory buffer to value"), __PRETTY_FUNCTION__); + val = buff; + } + else if (val.IsMemoryBuff()) { + wxLogTrace(traceMask, _T("(%s) concatenate memory buffer to value"), __PRETTY_FUNCTION__); + val.Cat(buff); + } + else { + AddError(_T("Memory buffer value cannot follow another value")); + } + + // store the input text's line number when the string was stored in 'val' + val.SetLineNo(m_lineNo); + + // read the next char after the closing quotes and returns it + if (ch >= 0) { + ch = ReadChar(is); + } + return ch; +} + + + + +#if defined( wxJSON_64BIT_INT ) +//! Converts a decimal string to a 64-bit signed integer +/*! + This function implements a simple variant + of the \b strtoll C-library function. + I needed this implementation because the wxString::To(U)LongLong + function does not work on my system: + + \li GNU/Linux Fedora Core 6 + \li GCC version 4.1.1 + \li libc.so.6 + + The wxWidgets library (actually I have installed version 2.8.7) + relies on \b strtoll in order to do the conversion from a string + to a long long integer but, in fact, it does not work because + the 'wxHAS_STRTOLL' macro is not defined on my system. + The problem only affects the Unicode builds while it seems + that the wxString::To(U)LongLong function works in ANSI builds. + + Note that this implementation is not a complete substitute of the + strtoll function because it only converts decimal strings (only base + 10 is implemented). + + @param str the string that contains the decimal literal + @param i64 the pointer to long long which holds the converted value + + @return TRUE if the conversion succeeds +*/ +bool +wxJSONReader::Strtoll(const wxString& str, wxInt64* i64) +{ + wxChar sign = ' '; + wxUint64 ui64; + bool r = DoStrto_ll(str, &ui64, &sign); + + // check overflow for signed long long + switch (sign) { + case '-': + if (ui64 > (wxUint64)LLONG_MAX + 1) { + r = false; + } + else { + *i64 = (wxInt64)(ui64 * -1); + } + break; + + // case '+' : + default: + if (ui64 > LLONG_MAX) { + r = false; + } + else { + *i64 = (wxInt64)ui64; + } + break; + } + return r; +} + + +//! Converts a decimal string to a 64-bit unsigned integer. +/*! + Similar to \c Strtoll but for unsigned integers +*/ +bool +wxJSONReader::Strtoull(const wxString& str, wxUint64* ui64) +{ + wxChar sign = ' '; + bool r = DoStrto_ll(str, ui64, &sign); + if (sign == '-') { + r = false; + } + return r; +} + +//! Perform the actual conversion from a string to a 64-bit integer +/*! + This function is called internally by the \c Strtoll and \c Strtoull functions + and it does the actual conversion. + The function is also able to check numeric overflow. + + @param str the string that has to be converted + @param ui64 the pointer to a unsigned long long that holds the converted value + @param sign the pointer to a wxChar character that will get the sign of the literal string, if any + @return TRUE if the conversion succeeds +*/ +bool +wxJSONReader::DoStrto_ll(const wxString& str, wxUint64* ui64, wxChar* sign) +{ + // the conversion is done by multiplying the individual digits + // in reverse order to the corresponding power of 10 + // + // 10's power: 987654321.9876543210 + // + // LLONG_MAX: 9223372036854775807 + // LLONG_MIN: -9223372036854775808 + // ULLONG_MAX: 18446744073709551615 + // + // the function does not take into account the sign: only a + // unsigned long long int is returned + + int maxDigits = 20; // 20 + 1 (for the sign) + + wxUint64 power10[] = { + wxULL(1), + wxULL(10), + wxULL(100), + wxULL(1000), + wxULL(10000), + wxULL(100000), + wxULL(1000000), + wxULL(10000000), + wxULL(100000000), + wxULL(1000000000), + wxULL(10000000000), + wxULL(100000000000), + wxULL(1000000000000), + wxULL(10000000000000), + wxULL(100000000000000), + wxULL(1000000000000000), + wxULL(10000000000000000), + wxULL(100000000000000000), + wxULL(1000000000000000000), + wxULL(10000000000000000000) + }; + + + wxUint64 temp1 = wxULL(0); // the temporary converted integer + + int strLen = str.length(); + if (strLen == 0) { + // an empty string is converted to a ZERO value: the function succeeds + *ui64 = wxLL(0); + return true; + } + + int index = 0; + wxChar ch = str[0]; + if (ch == '+' || ch == '-') { + *sign = ch; + ++index; + ++maxDigits; + } + + if (strLen > maxDigits) { + return false; + } + + // check the overflow: check the string length and the individual digits + // of the string; the overflow is checked for unsigned long long + if (strLen == maxDigits) { + wxString uLongMax(_T("18446744073709551615")); + int j = 0; + for (int i = index; i < strLen - 1; i++) { + ch = str[i]; + if (ch < '0' || ch > '9') { + return false; + } + if (ch > uLongMax[j]) { + return false; + } + if (ch < uLongMax[j]) { + break; + } + ++j; + } + } + + // get the digits in the reverse order and multiply them by the + // corresponding power of 10 + int exponent = 0; + for (int i = strLen - 1; i >= index; i--) { + wxChar c = str[i]; + if (c < '0' || c > '9') { + return false; + } + c = c - '0'; + // compute the new temporary value + temp1 += c * power10[exponent]; + ++exponent; + } + *ui64 = temp1; + return true; +} + +#endif // defined( wxJSON_64BIT_INT ) + +/* +{ +} +*/ + + + diff --git a/utils/json/jsonval.cpp b/utils/json/jsonval.cpp new file mode 100644 index 0000000..9fd870a --- /dev/null +++ b/utils/json/jsonval.cpp @@ -0,0 +1,3555 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: jsonval.cpp +// Purpose: the wxJSON class that holds a JSON value +// Author: Luciano Cattani +// Created: 2007/10/01 +// RCS-ID: $Id: jsonval.cpp,v 1.12 2008/03/06 10:25:18 luccat Exp $ +// Copyright: (c) 2007 Luciano Cattani +// Licence: wxWidgets licence +///////////////////////////////////////////////////////////////////////////// +#ifdef __GNUG__ +#pragma implementation "jsonval.cpp" +#endif + +#include "pgadmin3.h" + +// For compilers that support precompilation, includes "wx.h". + +#ifdef __BORLANDC__ +#pragma hdrstop +#endif + +#include +#include +#include + + + +WX_DEFINE_OBJARRAY(wxJSONInternalArray); + + +// the trace mask used in wxLogTrace() function +// static const wxChar* traceMask = _T("jsonval"); +static const wxChar* traceMask = _T("jsonval"); +static const wxChar* compareTraceMask = _T("sameas"); +static const wxChar* cowTraceMask = _T("traceCOW"); + + + +/******************************************************************* + + class wxJSONRefData + +*******************************************************************/ + + +/*! \class wxJSONRefData + \brief The reference counted JSON value data (internal use). + + Starting from version 0.4, the JSON value class use the reference + counting tecnique (also know as \e copy-on-write) described in the + \b wxWidgets documentation in order to speed up processing. + The class is used internally by the wxJSONValue class which does + all processing. + To know more about COW see \ref json_internals_cow +*/ + +#if defined( WXJSON_USE_VALUE_COUNTER ) +// The progressive counter (used for debugging only) +int wxJSONRefData::sm_progr = 1; +#endif + +//! Constructor. +wxJSONRefData::wxJSONRefData() +{ + m_lineNo = -1; + m_refCount = 1; + m_memBuff = 0; + +#if defined( WXJSON_USE_VALUE_COUNTER ) + m_progr = sm_progr; + ++sm_progr; + wxLogTrace(traceMask, _T("(%s) JSON refData ctor progr=%d"), + __PRETTY_FUNCTION__, m_progr); +#endif +} + +// Dtor +wxJSONRefData::~wxJSONRefData() +{ + if (m_memBuff) { + delete m_memBuff; + } +} + +// Return the number of objects that reference this data. +int +wxJSONRefData::GetRefCount() const +{ + return m_refCount; +} + + +/******************************************************************* + + class wxJSONValue + +*******************************************************************/ + + +/*! \class wxJSONValue + \brief The JSON value class implementation. + +This class holds a JSON value which may be of variuos types (see the +wxJSONType constants for a description of the types). +To know more about the internal representation of JSON values see +\ref pg_json_internals. + +Starting from version 0.5 the wxJSON library supports 64-bits integers on +platforms that have native support for very large integers. +Note that the integer type is still stored as a generic wxJSONTYPE_(U)INT +constant regardless the size of the value but the JSON value class defines +functions in order to let the user know if an integer value fits in 16, 32 +or 64 bit integer. +To know more about 64-bits integer support see \ref json_internals_integer + +Storing values in a JSON value object of this class is very simple. +The following is an example: +\code + wxJSONValue v( _T( "A string")); // store a string value in the object + wxString s = v.AsString(); // get the string value + + v = 12; // now 'v' contains an integer value + int i = v.AsInt(); // get the integer +\endcode + + \par The C-string JSON value object + + The wxJSONValue(const wxChar*) ctor allows you to create a JSON value + object that contains a string value which is stored as a + \e pointer-to-static-string. + Beware that this ctor DOES NOT copy the string: it only stores the + pointer in a data member and the pointed-to buffer is not deleted + by the dtor. + If the string is not static you have to use the wxJSONValue(const wxString&) + constructor. + + Also note that this does NOT mean that the value stored in this JSON + object cannot change: you can assign whatever other value you want, + an integer, a double or an array of values. + What I intended is that the pointed-to string must exist for the lifetime + of the wxJSONValue object. + The following code is perfectly legal: + \code + wxJSONvalue aString( "this is a static string" ); + aString = 10; + \endcode + To know more about this topic see \ref json_internals_cstring. + + Starting from version 1.3 the class can hold binary memory buffers + as an extension to the JSON syntax. Memory buffers are stored as + \b wxMemoryBuffer objects which contain binary data. The class + uses reference counting for the copy and assignment operation but + it is not a \e copy-on-write structure. + To know more about memory buffers read \ref wxjson_tutorial_memorybuff + + \sa the \ref wxjson_tutorial. +*/ + + +#if defined( WXJSON_USE_VALUE_COUNTER ) +// The progressive counter (used for debugging only) +int wxJSONValue::sm_progr = 1; +#endif + +//! Constructors. +/*! + The overloaded constructors allow the user to construct a JSON value + object that holds the specified value and type of value. + The default ctor construct a valid JSON object that constains a \b null + value. + + If you want to create an \b invalid JSON value object you have to use the + \c wxJSONValue( wxJSONTYPE_INVALID ) ctor. + Note that this object is not a valid JSON value - to know more about this + topic see the SetType() function. + + To create an empty array or key/value map use the following: + \code + wxJSONvalue v1( wxJSONTYPE_ARRAY ); + wxJSONvalue v2( wxJSONTYPE_OBJECT ); + \endcode +*/ +wxJSONValue::wxJSONValue() +{ + m_refData = 0; + Init(wxJSONTYPE_NULL); +} + +//! Initialize the JSON value class. +/*! + The function is called by the ctors and allocates a new instance of + the wxJSONRefData class and sets the type of the JSON value. + Note that only the type is set, not the value. + Also note that this function may be called from other memberfunctions + if the \c m_refData data member is NULL. +*/ +wxJSONRefData* +wxJSONValue::Init(wxJSONType type) +{ + wxJSONRefData* data = GetRefData(); + if (data != 0) { + UnRef(); + } + + // we allocate a new instance of the referenced data + data = new wxJSONRefData(); + wxJSON_ASSERT(data); + + // in release builds we do not have ASSERT so we check 'data' before + // using it + if (data) { + data->m_type = type; + data->m_commentPos = wxJSONVALUE_COMMENT_BEFORE; + } + SetRefData(data); + +#if defined( WXJSON_USE_VALUE_COUNTER ) + m_progr = sm_progr; + ++sm_progr; + wxLogTrace(cowTraceMask, _T("(%s) Init a new object progr=%d"), + __PRETTY_FUNCTION__, m_progr); +#endif + return data; +} + + +//! \overload wxJSONValue() +wxJSONValue::wxJSONValue(wxJSONType type) +{ + m_refData = 0; + Init(type); +} + +//! \overload wxJSONValue() +wxJSONValue::wxJSONValue(int i) +{ + m_refData = 0; + wxJSONRefData* data = Init(wxJSONTYPE_INT); + wxJSON_ASSERT(data); + if (data != 0) { + // the 'VAL_INT' macro expands to 'm_valLong' or 'm_valInt64' depending + // on 64-bits integer support being enabled on not + data->m_value.VAL_INT = i; + } +} + + +//! \overload wxJSONValue() +wxJSONValue::wxJSONValue(unsigned int ui) +{ + m_refData = 0; + wxJSONRefData* data = Init(wxJSONTYPE_UINT); + wxJSON_ASSERT(data); + if (data != 0) { + // the 'VAL_UINT' macro expands to 'm_valULong' or 'm_valUInt64' depending + // on 64-bits integer support being enabled on not + data->m_value.VAL_UINT = ui; + } +} + +//! \overload wxJSONValue() +wxJSONValue::wxJSONValue(short int i) +{ + m_refData = 0; + wxJSONRefData* data = Init(wxJSONTYPE_INT); + wxJSON_ASSERT(data); + if (data != 0) { + // the 'VAL_INT' macro expands to 'm_valLong' or 'm_valInt64' depending + // on 64-bits integer support being enabled on not + data->m_value.VAL_INT = i; + } +} + + +//! \overload wxJSONValue() +wxJSONValue::wxJSONValue(unsigned short ui) +{ + m_refData = 0; + wxJSONRefData* data = Init(wxJSONTYPE_UINT); + wxJSON_ASSERT(data); + if (data != 0) { + // the 'VAL_UINT' macro expands to 'm_valULong' or 'm_valUInt64' depending + // on 64-bits integer support being enabled on not + data->m_value.VAL_UINT = ui; + } +} + +//! \overload wxJSONValue() +wxJSONValue::wxJSONValue(bool b) +{ + m_refData = 0; + wxJSONRefData* data = Init(wxJSONTYPE_BOOL); + wxJSON_ASSERT(data); + if (data != 0) { + data->m_value.m_valBool = b; + } +} + +//! \overload wxJSONValue() +wxJSONValue::wxJSONValue(double d) +{ + m_refData = 0; + wxJSONRefData* data = Init(wxJSONTYPE_DOUBLE); + wxJSON_ASSERT(data); + if (data != 0) { + data->m_value.m_valDouble = d; + } +} + +//! \overload wxJSONValue() +wxJSONValue::wxJSONValue(const wxChar* str) +{ + m_refData = 0; + wxJSONRefData* data = Init(wxJSONTYPE_CSTRING); + wxJSON_ASSERT(data); + if (data != 0) { +#if !defined( WXJSON_USE_CSTRING ) + data->m_type = wxJSONTYPE_STRING; + data->m_valString.assign(str); +#else + data->m_value.m_valCString = str; +#endif + } +} + +//! \overload wxJSONValue() +wxJSONValue::wxJSONValue(const wxString& str) +{ + m_refData = 0; + wxJSONRefData* data = Init(wxJSONTYPE_STRING); + wxJSON_ASSERT(data); + if (data != 0) { + data->m_valString.assign(str); + } +} + +//! \overload wxJSONValue() +wxJSONValue::wxJSONValue(long int l) +{ + m_refData = 0; + wxJSONRefData* data = Init(wxJSONTYPE_INT); + wxJSON_ASSERT(data); + if (data != 0) { + data->m_value.VAL_INT = l; + } +} + +//! \overload wxJSONValue() +wxJSONValue::wxJSONValue(unsigned long int ul) +{ + m_refData = 0; + wxJSONRefData* data = Init(wxJSONTYPE_UINT); + wxJSON_ASSERT(data); + if (data != 0) { + data->m_value.VAL_UINT = ul; + } +} + +//! Construct a JSON value object of type \e memory \e buffer +/*! + Note that this ctor makes a deep copy of \c buff so changes made + to the original buffer does not reflect to the buffer stored in this + JSON value. +*/ +wxJSONValue::wxJSONValue(const wxMemoryBuffer& buff) +{ + m_refData = 0; + wxJSONRefData* data = Init(wxJSONTYPE_MEMORYBUFF); + wxJSON_ASSERT(data); + if (data != 0) { + data->m_memBuff = new wxMemoryBuffer(); + const void* ptr = buff.GetData(); + size_t buffLen = buff.GetDataLen(); + if (buffLen > 0) { + data->m_memBuff->AppendData(ptr, buffLen); + } + } +} + +//! Construct a JSON value object of type \e memory \e buffer +/*! + Note that this ctor makes a deep copy of \c buff so changes made + to the original buffer does not reflect to the buffer stored in this + JSON value. +*/ +wxJSONValue::wxJSONValue(const void* buff, size_t len) +{ + m_refData = 0; + wxJSONRefData* data = Init(wxJSONTYPE_MEMORYBUFF); + wxJSON_ASSERT(data); + if (data != 0 && len > 0) { + data->m_memBuff = new wxMemoryBuffer(); + data->m_memBuff->AppendData(buff, len); + } +} + +//! Copy constructor +/*! + The function copies the content of \c other in this + object. + Note that the JSON value object is not really copied; + the function calls Ref() in order to increment + the reference count of the \c wxJSONRefData structure. +*/ +wxJSONValue::wxJSONValue(const wxJSONValue& other) +{ + m_refData = 0; + Ref(other); + + // the progressive counter of the ctor is not copied from + // the other wxJSONValue object: only data is shared, the + // progressive counter is not shared because this object + // is a copy of 'other' and it has its own progressive +#if defined( WXJSON_USE_VALUE_COUNTER ) + m_progr = sm_progr; + ++sm_progr; + wxLogTrace(cowTraceMask, _T("(%s) Copy ctor - progr=%d other progr=%d"), + __PRETTY_FUNCTION__, m_progr, other.m_progr); +#endif +} + + +//! Dtor - calls UnRef(). +wxJSONValue::~wxJSONValue() +{ + UnRef(); +} + + +// functions for retreiving the value type: they are all 'const' + + +//! Return the type of the value stored in the object. +/*! + This function is the only one that does not ASSERT that the + \c m_refData data member is not NULL. + In fact, if the JSON value object does not contain a pointer + to a wxJSONRefData structure, the function returns the + wxJSONTYPE_INVALID constant which represent an invalid JSON value object. + Also note that the pointer to the referenced data structure + should NEVER be NULL. + + \par Integer types + + Integers are stored internally in a \b signed/unsigned \b long \b int + or, on platforms that support 64-bits integers, in a + \b wx(U)Int64 data type. + When constructed, it is assigned a generic integer type that only + depends on the sign: wxJSON_(U)INT regardless the size of the + stored value. + + This function can be used to know the actual size requirement + of the stored value and how it can be retrieved. The value + returned by this function is: + + - for signed integers: + - \b wxJSONTYPE_SHORT if the value is between SHORT_MIN and SHORT_MAX + - \b wxJSONTYPE_LONG if the value is between LONG_MIN and LONG_MAX + and greater than SHORT_MAX and less than SHORT_MIN + - \b wxJSONTYPE_INT64 if the value is greater than LONG_MAX and + less than LONG_MIN + + - for unsigned integers: + - \b wxJSONTYPE_USHORT if the value is between 0 and USHORT_MAX + - \b wxJSONTYPE_ULONG if the value is between 0 and ULONG_MAX + and greater than USHORT_MAX + - \b wxJSONTYPE_UINT64 if the value is greater than ULONG_MAX + + Note that this function never returns the wxJSONTYPE_(U)INT constant + because the \b int data type may have the same width as SHORT or LONG + depending on the platform. + This does not mean that you cannot use \b int as the return value: if + you use \b wxWidgets to develop application in only one platform, you + can use \b int because you know the size of the data type. + Otherwise, if is preferable to always use \b long instead of \b int. + + Also note that the class defines the \c IsInt() memberfunction which + works fine regardless the actual width of the \b int data type. + This function returns TRUE if the stored value fits in a \b int data + type whatever its size is on the current platform (16 or 32-bits). + + \sa SetType IsInt +*/ +wxJSONType +wxJSONValue::GetType() const +{ + wxJSONRefData* data = GetRefData(); + wxJSONType type = wxJSONTYPE_INVALID; + if (data) { + type = data->m_type; + + // for integers and unsigned ints check the storage requirements + // note that ints are stored as 'long' or as 'long long' + switch (type) { + case wxJSONTYPE_INT: + // check if the integer fits in a SHORT INT + if (data->m_value.VAL_INT >= SHORT_MIN && + data->m_value.VAL_INT <= SHORT_MAX) { + type = wxJSONTYPE_SHORT; + } + // check if the value fits in LONG INT + else if (data->m_value.VAL_INT >= LONG_MIN + && data->m_value.VAL_INT <= LONG_MAX) { + type = wxJSONTYPE_LONG; + } + else { + type = wxJSONTYPE_INT64; + } + break; + + case wxJSONTYPE_UINT: + if (data->m_value.VAL_UINT <= USHORT_MAX) { + type = wxJSONTYPE_USHORT; + } + else if (data->m_value.VAL_UINT <= ULONG_MAX) { + type = wxJSONTYPE_ULONG; + } + else { + type = wxJSONTYPE_UINT64; + } + break; + + default: + break; + } + } + return type; +} + + +//! Return TRUE if the type of the value is wxJSONTYPE_NULL. +bool +wxJSONValue::IsNull() const +{ + wxJSONType type = GetType(); + bool r = false; + if (type == wxJSONTYPE_NULL) { + r = true; + } + return r; +} + + +//! Return TRUE if the value stored is valid +/*! + The function returns TRUE if the wxJSONValue object was correctly + initialized - that is it contains a valid value. + A JSON object is valid if its type is not equal to wxJSONTYPE_INVALID. + Please note that the default ctor of wxJSONValue constructs a \b valid + JSON object of type \b null. + To create an invalid object you have to use; + \code + wxJSONValue v( wxJSONTYPE_INVALID ); + \endcode +*/ +bool +wxJSONValue::IsValid() const +{ + wxJSONType type = GetType(); + bool r = false; + if (type != wxJSONTYPE_INVALID) { + r = true; + } + return r; +} + +//! Return TRUE if the type of the value stored is integer. +/*! + This function returns TRUE if the stored value is of + type signed integer and the numeric value fits in a + \b int data type. + In other words, the function returns TRUE if the \c wxJSONRefData::m_type + data member is of type \c wxJSONTYPE_INT and: + + \code + INT_MIN <= m_value <= INT_MAX + \endcode + + Note that if you are developing cross-platform applications you should never + use \b int as the integer data type but \b long for 32-bits integers and + \b short for 16-bits integers. + This is because the \b int data type may have different width on different + platforms. + Regardless the widht of the data type (16 or 32 bits), the function returns + the correct result because it relies on the INT_MAX and INT_MIN macros. + + \sa \ref json_internals_integer +*/ +bool +wxJSONValue::IsInt() const +{ + wxJSONType type = GetType(); + bool r = false; + // if the type is SHORT the value fits into an INT, too + if (type == wxJSONTYPE_SHORT) { + r = true; + } + else if (type == wxJSONTYPE_LONG) { + // in case of LONG, check if the bit width is the same + if (INT_MAX == LONG_MAX) { + r = true; + } + } + return r; +} + +//! Return TRUE if the type of the value stored is 16-bit integer. +/*! + This function returns TRUE if the stored value is of + type signed integer and the numeric value fits in a + \b short \b int data type (16-bit integer). + In other words, the function returns TRUE if the \c wxJSONRefData::m_type + data member is of type \c wxJSONTYPE_INT and: + + \code + SHORT_MIN <= m_value <= SHORT_MAX + \endcode + + \sa \ref json_internals_integer +*/ +bool +wxJSONValue::IsShort() const +{ + wxJSONType type = GetType(); + bool r = false; + if (type == wxJSONTYPE_SHORT) { + r = true; + } + return r; +} + +//! Return TRUE if the type of the value stored is a unsigned int. +/*! + This function returns TRUE if the stored value is of + type unsigned integer and the numeric value fits int a + \b int data type. + In other words, the function returns TRUE if the \c wxJSONRefData::m_type + data member is of type \c wxJSONTYPE_UINT and: + + \code + 0 <= m_value <= UINT_MAX + \endcode + + Note that if you are developing cross-platform applications you should never + use \b unsigned \b int as the integer data type but \b unsigned \b long for + 32-bits integers and \b unsigned \b short for 16-bits integers. + This is because the \b unsigned \b int data type may have different width + on different platforms. + Regardless the widht of the data type (16 or 32 bits), the function returns + the correct result because it relies on the UINT_MAX macro. + + + \sa \ref json_internals_integer +*/ +bool +wxJSONValue::IsUInt() const +{ + wxJSONType type = GetType(); + bool r = false; + if (type == wxJSONTYPE_USHORT) { + r = true; + } + else if (type == wxJSONTYPE_ULONG) { + if (INT_MAX == LONG_MAX) { + r = true; + } + } + return r; +} + +//! Return TRUE if the type of the value stored is a unsigned short. +/*! + This function returns TRUE if the stored value is of + type unsigned integer and the numeric value fits in a + \b unsigned \b short \b int data type. + In other words, the function returns TRUE if the \c wxJSONRefData::m_type + data member is of type \c wxJSONTYPE_UINT and: + + \code + 0 <= m_value <= USHORT_MAX + \endcode + + \sa \ref json_internals_integer +*/ +bool +wxJSONValue::IsUShort() const +{ + wxJSONType type = GetType(); + bool r = false; + if (type == wxJSONTYPE_USHORT) { + r = true; + } + return r; +} + + +//! Return TRUE if the stored value is an integer which fits in a long int +/*! + This function returns TRUE if the stored value is of + type signed LONG integer and the numeric value fits int a + \b long \b int data type. + In other words, the function returns TRUE if the \c wxJSONRefData::m_type + data member is of type \c wxJSONTYPE_INT and: + + \code + LONG_MIN <= m_value <= LONG_MAX + \endcode + + \sa \ref json_internals_integer +*/ +bool +wxJSONValue::IsLong() const +{ + wxJSONType type = GetType(); + bool r = false; + if (type == wxJSONTYPE_LONG || type == wxJSONTYPE_SHORT) { + r = true; + } + return r; +} + +//! Return TRUE if the stored value is an integer which fits in a unsigned long int +/*! + This function returns TRUE if the stored value is of + type unsigned LONG integer and the numeric value fits int a + \b unsigned \b long \b int data type. + In other words, the function returns TRUE if the \c wxJSONRefData::m_type + data member is of type \c wxJSONTYPE_UINT and: + + \code + 0 <= m_value <= ULONG_MAX + \endcode + + \sa \ref json_internals_integer +*/ +bool +wxJSONValue::IsULong() const +{ + wxJSONType type = GetType(); + bool r = false; + if (type == wxJSONTYPE_ULONG || type == wxJSONTYPE_USHORT) { + r = true; + } + return r; +} + + + +//! Return TRUE if the type of the value stored is a boolean. +bool +wxJSONValue::IsBool() const +{ + wxJSONType type = GetType(); + bool r = false; + if (type == wxJSONTYPE_BOOL) { + r = true; + } + return r; +} + +//! Return TRUE if the type of the value stored is a double. +bool +wxJSONValue::IsDouble() const +{ + wxJSONType type = GetType(); + bool r = false; + if (type == wxJSONTYPE_DOUBLE) { + r = true; + } + return r; +} + +//! Return TRUE if the type of the value stored is a wxString object. +bool +wxJSONValue::IsString() const +{ + wxJSONType type = GetType(); + bool r = false; + if (type == wxJSONTYPE_STRING) { + r = true; + } + return r; +} + +//! Return TRUE if the type of the value stored is a pointer to a static C string. +/*! + This function returns TRUE if, and only if the stored value is a + pointer to a static C-string and the C-string storage is enabled in + the wxJSON library. + By default, C-string storage is not enabled in the library so this + function always returns FALSE. + To know more about C-strings read \ref json_internals_cstring +*/ +bool +wxJSONValue::IsCString() const +{ + wxJSONType type = GetType(); + bool r = false; + if (type == wxJSONTYPE_CSTRING) { + r = true; + } + return r; +} + +//! Return TRUE if the type of the value stored is an array type. +bool +wxJSONValue::IsArray() const +{ + wxJSONType type = GetType(); + bool r = false; + if (type == wxJSONTYPE_ARRAY) { + r = true; + } + return r; +} + +//! Return TRUE if the type of this value is a key/value map. +bool +wxJSONValue::IsObject() const +{ + wxJSONType type = GetType(); + bool r = false; + if (type == wxJSONTYPE_OBJECT) { + r = true; + } + return r; +} + +//! Return TRUE if the type of this value is a binary memory buffer. +bool +wxJSONValue::IsMemoryBuff() const +{ + wxJSONType type = GetType(); + bool r = false; + if (type == wxJSONTYPE_MEMORYBUFF) { + r = true; + } + return r; +} + + + +// get the stored value; all these functions are 'const' + +//! Return the stored value as an integer. +/*! + The function returns the stored value as an integer. + Note that the function does not check that the type of the + value is actually an integer and it just returns the content + of the wxJSONValueHolder union. + However, in debug builds, the function ASSERTs that the + type of the stored value \c IsInt(). + + \sa \ref json_internals_integer + \sa \ref wxjson_tutorial_get +*/ +int +wxJSONValue::AsInt() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + int i = (int)data->m_value.VAL_INT; + + wxJSON_ASSERT(IsInt()); + return i; +} + +//! Return the stored value as a boolean. +/*! + The function returns the stored value as a boolean. + Note that the function does not check that the type of the + value is actually a boolean and it just returns the content + of the wxJSONValueHolder union. + However, in debug builds, the function ASSERTs that the + type of the stored value is wxJSONTYPE_BOOL. + + \sa \ref wxjson_tutorial_get +*/ +bool +wxJSONValue::AsBool() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + wxJSON_ASSERT(data->m_type == wxJSONTYPE_BOOL); + return data->m_value.m_valBool; +} + +//! Return the stored value as a double. +/*! + The function returns the stored value as a double. + Note that the function does not check that the type of the + value is actually a double and it just returns the content + of the wxJSONValueHolder union as if it was a double. + However, in debug builds, the function ASSERTs that the + type of the stored value \c IsDouble(). + + \sa \ref wxjson_tutorial_get +*/ +double +wxJSONValue::AsDouble() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + double d = data->m_value.m_valDouble; + wxJSON_ASSERT(IsDouble()); + return d; +} + + +//! Return the stored value as a wxWidget's string. +/*! + The function returns a string representation of the value + stored in the JSON object. + All value types are converted to a string by this function + and returned as a string: + + \li For integer the string is the string representation of + the numerical value in decimal notation; the function uses the + \b wxString::Printf() function for the conversion + + \li for doubles, the value is converted to a string using the + \b wxString::Printf("%.10g") function; the format string specifies + a precision of ten decimal digits and suppress trailing ZEROes + + \li for booleans the string returned is: \b true or \b false. + + \li if the value is a NULL value the \b null literal string is returned. + + \li if the value is of type wxJSONTYPE_INVALID, the literal string \b <invalid> + is returned. Note that this is NOT a valid JSON text. + + \li if the value is of type wxJSONTYPE_MEMORYBUFF the string returned contains the + hexadecimal digits of the first 5 bytes preceeded by the length of the buffer, + enclosed in parenthesis + + If the value is an array or map, the returned string is the number of + elements is the array/object enclosed in the JSON special characters that + identifies the array/object. Example: + + \code + [0] // an empty array + {12} // an object of 12 elements + \endcode + + \sa \ref wxjson_tutorial_get +*/ +wxString +wxJSONValue::AsString() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + wxString s; + int size = Size(); + switch (data->m_type) { + case wxJSONTYPE_STRING: + s.assign(data->m_valString); + break; + case wxJSONTYPE_CSTRING: + s.assign(data->m_value.m_valCString); + break; + case wxJSONTYPE_INT: +#if defined( wxJSON_64BIT_INT ) + s.Printf(_T("%") wxLongLongFmtSpec _T("i"), + data->m_value.m_valInt64); +#else + s.Printf(_T("%ld"), data->m_value.m_valLong); +#endif + break; + case wxJSONTYPE_UINT: +#if defined( wxJSON_64BIT_INT ) + s.Printf(_T("%") wxLongLongFmtSpec _T("u"), + data->m_value.m_valUInt64); +#else + s.Printf(_T("%lu"), data->m_value.m_valULong); +#endif + break; + case wxJSONTYPE_DOUBLE: + s.Printf(_T("%.10g"), data->m_value.m_valDouble); + break; + case wxJSONTYPE_BOOL: + s.assign((data->m_value.m_valBool ? + _T("true") : _T("false"))); + break; + case wxJSONTYPE_NULL: + s.assign(_T("null")); + break; + case wxJSONTYPE_INVALID: + s.assign(_T("")); + break; + case wxJSONTYPE_ARRAY: + s.Printf(_T("[%d]"), size); + break; + case wxJSONTYPE_OBJECT: + s.Printf(_T("{%d}"), size); + break; + case wxJSONTYPE_MEMORYBUFF: + s = MemoryBuffToString(*(data->m_memBuff), 5); + break; + default: + s.assign(_T("wxJSONValue::AsString(): Unknown JSON type \'")); + s.append(TypeToString(data->m_type)); + s.append(_T("\'")); + wxFAIL_MSG(s); + break; + } + return s; +} + +//! Return the stored value as a pointer to a static C string. +/*! + If the type of the value is stored as a C-string data type the + function just returns that pointer. + If the stored value is a wxString object, the function returns the + pointer returned by the \b wxString::c_str() function. + If the stored value is of all other JSON types, the functions returns a NULL pointer. + + Note that in versions prior to 0.5, the + function returned a NULL pointer also if the value is a \c wxString object. + + \sa \ref json_internals_cstring + \sa \ref wxjson_tutorial_get + +*/ +const wxChar* +wxJSONValue::AsCString() const +{ + const wxChar* s = 0; + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + switch (data->m_type) { + case wxJSONTYPE_CSTRING: + s = data->m_value.m_valCString; + break; + case wxJSONTYPE_STRING: + s = data->m_valString.c_str(); + break; + default: + break; + } + return s; +} + + +//! Return the stored value as a unsigned int. +/*! + The function returns the stored value as a unsigned integer. + Note that the function does not check that the type of the + value is actually a unsigned integer and it just returns the content + of the wxJSONValueHolder union. + However, in debug builds, the function ASSERTs that the + type of the stored value is wxJSONTYPE_UINT. + + \sa \ref json_internals_integer + \sa \ref wxjson_tutorial_get +*/ +unsigned int +wxJSONValue::AsUInt() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + unsigned int ui = (unsigned)data->m_value.VAL_UINT; + + wxJSON_ASSERT(IsUInt()); + return ui; +} + + +//! Returns the value as a long integer +/*! + The function returns the stored value as a long integer. + Note that the function does not check that the type of the + value is actually a long integer and it just returns the content + of the wxJSONValueHolder union. + However, in debug builds, the function ASSERTs that the + type of the stored value \c IsLong(). + + \sa \ref json_internals_integer + \sa \ref wxjson_tutorial_get +*/ +long int +wxJSONValue::AsLong() const +{ + long int l; + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + l = (long)data->m_value.VAL_INT; + + wxJSON_ASSERT(IsLong()); + return l; +} + +//! Returns the value as a unsigned long integer +/*! + The function returns the stored value as a unsigned long integer. + Note that the function does not check that the type of the + value is actually a unsigned long integer and it just returns the content + of the wxJSONValueHolder union. + However, in debug builds, the function ASSERTs that the + type of the stored value \c IsLong(). + + \sa \ref json_internals_integer + \sa \ref wxjson_tutorial_get +*/ +unsigned long int +wxJSONValue::AsULong() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + unsigned long int ul = (unsigned long)data->m_value.VAL_UINT; + + wxJSON_ASSERT(IsULong()); // expands only in debug builds + return ul; +} + + +//! Returns the value as a short integer +/*! + The function returns the stored value as a short integer. + Note that the function does not check that the type of the + value is actually a short integer and it just returns the content + of the wxJSONValueHolder union. + However, in debug builds, the function ASSERTs that the + type of the stored value \c IsShort(). + + \sa \ref json_internals_integer + \sa \ref wxjson_tutorial_get +*/ +short int +wxJSONValue::AsShort() const +{ + short int i; + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + i = (short)data->m_value.VAL_INT; + + wxJSON_ASSERT(IsShort()); + return i; +} + +//! Returns the value as a unsigned short integer +/*! + The function returns the stored value as a unsigned short integer. + Note that the function does not check that the type of the + value is actually a unsigned short and it just returns the content + of the wxJSONValueHolder union. + However, in debug builds, the function ASSERTs that the + type of the stored value \c IsUShort(). + + \sa \ref json_internals_integer + \sa \ref wxjson_tutorial_get +*/ +unsigned short +wxJSONValue::AsUShort() const +{ + unsigned short ui; + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + ui = (unsigned short)data->m_value.VAL_UINT; + + wxJSON_ASSERT(IsUShort()); + return ui; +} + + + +//! Stores the value of this object in the provided argument +/*! + The functions of the form \c AsXxxxxx(T&) are the same as the \c AsXxxxxxx() + but store the value in the provided argument and return TRUE if the value of + this object is of the correct type. + By using these functions you can get the value and test if the JSON value is + of the expected type in only one step. + For example: + \code + int i; wxJSONValue v(10); + if ( !v.AsInt( i )) { + cout << "Error: value is not of the expected type"; + } + \endcode + This is the same as: + \code + int i; wxJSONValue v(10); + if ( v.IsInt() { + i = v.AsInt(); + } + else { + cout << "Error: value is not of the expected type"; + } + \endcode + Thanks to \b catalin who has suggested this new feature. +*/ +bool +wxJSONValue::AsInt(int& i) const +{ + bool r = false; + if (IsInt()) { + i = AsInt(); + r = true; + } + return r; +} + +bool +wxJSONValue::AsUInt(unsigned int& ui) const +{ + bool r = false; + if (IsUInt()) { + ui = AsUInt(); + r = true; + } + return r; +} + +bool +wxJSONValue::AsShort(short int& s) const +{ + bool r = false; + if (IsShort()) { + s = AsShort(); + r = true; + } + return r; +} + +bool +wxJSONValue::AsUShort(unsigned short& us) const +{ + bool r = false; + if (IsUShort()) { + us = AsUShort(); + r = true; + } + return r; +} + +bool +wxJSONValue::AsLong(long int& l) const +{ + bool r = false; + if (IsLong()) { + l = AsLong(); + r = true; + } + return r; +} + +bool +wxJSONValue::AsULong(unsigned long& ul) const +{ + bool r = false; + if (IsULong()) { + ul = AsULong(); + r = true; + } + return r; +} + + +bool +wxJSONValue::AsBool(bool& b) const +{ + bool r = false; + if (IsBool()) { + b = AsBool(); + r = true; + } + return r; +} + +bool +wxJSONValue::AsDouble(double& d) const +{ + bool r = false; + if (IsDouble()) { + d = AsDouble(); + r = true; + } + return r; +} + +//! Return this string value in the provided argument +/*! + This function is different from \c AsString because the latter always returns + a string also when this object does not contain a string. In that case, a string + representation of this value is returned. + This function, instead, returns TRUE only if this object contains a string, that is + only if \c IsString() returns TRUE. + Also note that the string value is only stored in \c str if this object actually + contains a \b string or \b c-string value. + \c str will never contain a string representation of other types. +*/ +bool +wxJSONValue::AsString(wxString& str) const +{ + bool r = IsString(); + if (r) { + str = AsString(); + } + return r; +} + +bool +wxJSONValue::AsCString(wxChar* ch) const +{ + bool r = IsCString(); + if (r) { + ch = (wxChar*)AsCString(); + } + return r; +} + +//! Returns the value as a memory buffer +/*! + The function returns the \e memory \e buffer object stored in + this JSON object. + Note that as of wxWidgets 2.8 and 2.9 the \b wxMemoryBuffer object uses + reference counting when copying the actual buffer but the class itself + is not a \e copy-on-write structure so changes made to one buffer affects + all other copies made from it. + This means that if you make a change to the returned copy of the memory + buffer, the change affects also the memory buffer stored in this JSON value. + + If this JSON object does not contain a \e wxJSONTYPE_MEMORYBUFF type + the function returns an empty memory buffer object. + An empty memory buffer is also returned if this JSON + type contains a valid, empty memory buffer. + You have to use the IsMemoryBuff() function to known the type of the + JSON value contained in this object, or the overloaded version of + this function. +*/ +wxMemoryBuffer +wxJSONValue::AsMemoryBuff() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + wxMemoryBuffer buff; + if (data->m_memBuff) { + buff = *(data->m_memBuff); + } + + wxJSON_ASSERT(IsMemoryBuff()); + return buff; +} + + +//! Returns the value as a memory buffer +/*! + The function returns the \e memory \e buffer object stored in + this JSON object. + Note that as of wxWidgets 2.8 and 2.9 the \b wxMemoryBuffer object uses + reference counting when copying the actual buffer but the class itself + is not a \e copy-on-write structure so changes made to one buffer affects + all other copies made from it. + This means that if you make a change to the returned copy of the memory + buffer, the change affects also the memory buffer stored in this JSON value. + + If this JSON object does not contain a \e wxJSONTYPE_MEMORYBUFF type + the function returns an empty memory buffer object. + An empty memory buffer is also returned if this JSON + type contains a valid, empty memory buffer. + You have to use the IsMemoryBuff() function to known the type of the + JSON value contained in this object, or the overloaded version of + this function. +*/ +bool +wxJSONValue::AsMemoryBuff(wxMemoryBuffer& buff) const +{ + bool r = IsMemoryBuff(); + if (r) { + buff = AsMemoryBuff(); + } + return r; +} + + +// internal use + +//! Return the stored value as a map object. +/*! + This function is for testing and debugging purposes and you shold never use it. + To retreive values from an array or map JSON object use the \c Item() or ItemAt() + memberfunctions or the subscript operator. + If the stored value is not a map type, returns a NULL pointer. +*/ +const wxJSONInternalMap* +wxJSONValue::AsMap() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + + const wxJSONInternalMap* v = 0; + if (data->m_type == wxJSONTYPE_OBJECT) { + v = &(data->m_valMap); + } + return v; +} + +//! Return the stored value as an array object. +/*! + This function is for testing and debugging purposes and you shold never use it. + To retreive values from an array or map JSON object use the \c Item() or ItemAt() + memberfunctions or the subscript operator. + If the stored value is not an array type, returns a NULL pointer. +*/ +const wxJSONInternalArray* +wxJSONValue::AsArray() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + + const wxJSONInternalArray* v = 0; + if (data->m_type == wxJSONTYPE_ARRAY) { + v = &(data->m_valArray); + } + return v; +} + +// retrieve the members and other info + + +//! Return TRUE if the object contains an element at the specified index. +/*! + If the stoerd value is not an array or a map, the function returns FALSE. +*/ +bool +wxJSONValue::HasMember(unsigned index) const +{ + bool r = false; + int size = Size(); + if (index < (unsigned)size) { + r = true; + } + return r; +} + +//! Return TRUE if the object contains an element at the specified key. +/*! + If the stored value is not a key/map map, the function returns FALSE. +*/ +bool +wxJSONValue::HasMember(const wxString& key) const +{ + bool r = false; + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + + if (data && data->m_type == wxJSONTYPE_OBJECT) { + wxJSONInternalMap::iterator it = data->m_valMap.find(key); + if (it != data->m_valMap.end()) { + r = true; + } + } + return r; +} + +//! Return the size of the array or map stored in this value. +/*! + Note that both the array and the key/value map may have a size of + ZERO elements. + If the stored value is not an array nor a key/value hashmap, the + function returns -1. +*/ +int +wxJSONValue::Size() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + + int size = -1; + if (data->m_type == wxJSONTYPE_ARRAY) { + size = (int)data->m_valArray.GetCount(); + } + if (data->m_type == wxJSONTYPE_OBJECT) { + size = (int)data->m_valMap.size(); + } + return size; +} + +//! Return the array of keys of this JSON object. +/*! + If the stored value is a key/value map, the function returns an + array of strings containing the \e key of all elements. + Note that the returned array may be empty if the map has ZERO + elements. + An empty string array is also returned if the stored value is + not a key/value map. + Also note that in debug builds, the function wxJSON_ASSERTs that the + type of the stored object is wxJSONTYPE_OBJECT. +*/ +wxArrayString +wxJSONValue::GetMemberNames() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + wxJSON_ASSERT(data->m_type == wxJSONTYPE_OBJECT); + + wxArrayString arr; + if (data->m_type == wxJSONTYPE_OBJECT) { + wxJSONInternalMap::iterator it; + for (it = data->m_valMap.begin(); it != data->m_valMap.end(); it++) { + arr.Add(it->first); + } + } + return arr; +} + + +// appending items, resizing and deleting items +// NOTE: these functions are not 'const' so we have to call +// the COW() function before accessing data + +//! Append the specified value in the array. +/*! + The function appends the value specified in the parameter to the array + contained in this object. + If this object does not contain an array type, the actual content is + deleted, a new array type is created and the JSON value \c value is + appended to the newly created array. + Returns a reference to the appended object. +*/ +wxJSONValue& +wxJSONValue::Append(const wxJSONValue& value) +{ + wxJSONRefData* data = COW(); + wxJSON_ASSERT(data); + if (data->m_type != wxJSONTYPE_ARRAY) { + // we have to change the type of the actual object to the array type + SetType(wxJSONTYPE_ARRAY); + } + // we add the wxJSONValue object to the wxObjArray: note that the + // array makes a copy of the JSON-value object by calling its + // copy ctor thus using reference count + data->m_valArray.Add(value); + wxJSONValue& v = data->m_valArray.Last(); + return v; +} + + +//! \overload Append( const wxJSONValue& ) +wxJSONValue& +wxJSONValue::Append(int i) +{ + wxJSONValue v(i); + wxJSONValue& r = Append(v); + return r; +} + +//! \overload Append( const wxJSONValue& ) +wxJSONValue& +wxJSONValue::Append(short int i) +{ + wxJSONValue v(i); + wxJSONValue& r = Append(v); + return r; +} + +//! \overload Append( const wxJSONValue& ) +wxJSONValue& +wxJSONValue::Append(long int l) +{ + wxJSONValue v(l); + wxJSONValue& r = Append(v); + return r; +} + +//! \overload Append( const wxJSONValue& ) +wxJSONValue& +wxJSONValue::Append(bool b) +{ + wxJSONValue v(b); + wxJSONValue& r = Append(v); + return r; +} + +//! \overload Append( const wxJSONValue& ) +wxJSONValue& +wxJSONValue::Append(unsigned int ui) +{ + wxJSONValue v(ui); + wxJSONValue& r = Append(v); + return r; +} + +//! \overload Append( const wxJSONValue& ) +wxJSONValue& +wxJSONValue::Append(unsigned short ui) +{ + wxJSONValue v(ui); + wxJSONValue& r = Append(v); + return r; +} + +//! \overload Append( const wxJSONValue& ) +wxJSONValue& +wxJSONValue::Append(unsigned long ul) +{ + wxJSONValue v(ul); + wxJSONValue& r = Append(v); + return r; +} + +//! \overload Append( const wxJSONValue& ) +wxJSONValue& +wxJSONValue::Append(double d) +{ + wxJSONValue v(d); + wxJSONValue& r = Append(v); + return r; +} + +//! \overload Append( const wxJSONValue& ) +wxJSONValue& +wxJSONValue::Append(const wxChar* str) +{ + wxJSONValue v(str); + wxJSONValue& r = Append(v); + return r; +} + +//! \overload Append( const wxJSONValue& ) +wxJSONValue& +wxJSONValue::Append(const wxString& str) +{ + wxJSONValue v(str); + wxJSONValue& r = Append(v); + return r; +} + +//! \overload Append( const wxJSONValue& ) +wxJSONValue& +wxJSONValue::Append(const wxMemoryBuffer& buff) +{ + wxJSONValue v(buff); + wxJSONValue& r = Append(v); + return r; +} + +//! \overload Append( const wxJSONValue& ) +wxJSONValue& +wxJSONValue::Append(const void* buff, size_t len) +{ + wxJSONValue v(buff, len); + wxJSONValue& r = Append(v); + return r; +} + + +//! Concatenate a string to this string object. +/*! + The function concatenates \c str to the string contained + in this object and returns TRUE if the operation is succefull. + If the value stored in this value is not a string object + the function does nothing and returns FALSE. + Note that in order to be successfull, the value must contain + a \b wxString object and not a pointer to C-string. +*/ +bool +wxJSONValue::Cat(const wxString& str) +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + + bool r = false; + if (data->m_type == wxJSONTYPE_STRING) { + wxJSONRefData* dataC = COW(); + wxJSON_ASSERT(dataC); + dataC->m_valString.append(str); + r = true; + } + return r; +} + +//! Concatenate a memory buffer to this memory buffer object. +/*! + The function concatenates \c buff to the \b wxMemoryBuffer object contained + in this object and returns TRUE if the operation is succefull. + If the value stored in this value is not a memory buffer object + the function does nothing and returns FALSE. +*/ +bool +wxJSONValue::Cat(const wxMemoryBuffer& buff) +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + + bool r = false; + if (data->m_type == wxJSONTYPE_MEMORYBUFF) { + wxJSONRefData* dataC = COW(); + wxJSON_ASSERT(dataC); + dataC->m_memBuff->AppendData(buff.GetData(), buff.GetDataLen()); + r = true; + } + return r; +} + + +//! \overload Cat( const wxString& ) +bool +wxJSONValue::Cat(const wxChar* str) +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + + bool r = false; + if (data->m_type == wxJSONTYPE_STRING) { + wxJSONRefData* dataC = COW(); + wxJSON_ASSERT(dataC); + dataC->m_valString.append(str); + r = true; + } + return r; +} + + +//! Remove the item at the specified index or key. +/*! + The function removes the item at index \c index or at the specified + key in the array or map. + If this object does not contain an array (for a index parameter) or a map + (for a key parameter), the function does nothing and returns FALSE. + If the element does not exist, FALSE is returned. +*/ +bool +wxJSONValue::Remove(int index) +{ + wxJSONRefData* data = COW(); + wxJSON_ASSERT(data); + + bool r = false; + if (data->m_type == wxJSONTYPE_ARRAY) { + data->m_valArray.RemoveAt(index); + r = true; + } + return r; +} + + +//! \overload Remove( int ) +bool +wxJSONValue::Remove(const wxString& key) +{ + wxJSONRefData* data = COW(); + wxJSON_ASSERT(data); + + bool r = false; + if (data->m_type == wxJSONTYPE_OBJECT) { + wxJSONInternalMap::size_type count = data->m_valMap.erase(key); + if (count > 0) { + r = true; + } + } + return r; +} + + +//! Clear the object value. +/*! + This function causes the object to be empty. + The function simply calls UnRef() making this object to become + invalid and set its type to wxJSONTYPE_INVALID. +*/ +void +wxJSONValue::Clear() +{ + UnRef(); + SetType(wxJSONTYPE_INVALID); +} + +// retrieve an item + +//! Return the item at the specified index. +/*! + The function returns a reference to the object at the specified + index. + If the element does not exist, the array is enlarged to \c index + 1 + elements and a reference to the last element will be returned. + New elements will contain NULL values. + If this object does not contain an array, the old value is + replaced by an array object which will be enlarged to the needed + dimension. +*/ +wxJSONValue& +wxJSONValue::Item(unsigned index) +{ + wxJSONRefData* data = COW(); + wxJSON_ASSERT(data); + + if (data->m_type != wxJSONTYPE_ARRAY) { + data = SetType(wxJSONTYPE_ARRAY); + } + int size = Size(); + wxJSON_ASSERT(size >= 0); + // if the desired element does not yet exist, we create as many + // elements as needed; the new values will be 'null' values + if (index >= (unsigned)size) { + wxJSONValue v(wxJSONTYPE_NULL); + int missing = index - size + 1; + data->m_valArray.Add(v, missing); + } + return data->m_valArray.Item(index); +} + +//! Return the item at the specified key. +/*! + The function returns a reference to the object in the map + that has the specified key. + If \c key does not exist, a new NULL value is created with + the provided key and a reference to it is returned. + If this object does not contain a map, the old value is + replaced by a map object. +*/ +wxJSONValue& +wxJSONValue::Item(const wxString& key) +{ + wxLogTrace(traceMask, _T("(%s) searched key=\'%s\'"), __PRETTY_FUNCTION__, key.c_str()); + wxLogTrace(traceMask, _T("(%s) actual object: %s"), __PRETTY_FUNCTION__, GetInfo().c_str()); + + wxJSONRefData* data = COW(); + wxJSON_ASSERT(data); + + if (data->m_type != wxJSONTYPE_OBJECT) { + // deletes the contained value; + data = SetType(wxJSONTYPE_OBJECT); + return data->m_valMap[key]; + } + wxLogTrace(traceMask, _T("(%s) searching key \'%s' in the actual object"), + __PRETTY_FUNCTION__, key.c_str()); + return data->m_valMap[key]; +} + + +//! Return the item at the specified index. +/*! + The function returns a copy of the object at the specified + index. + If the element does not exist, the function returns an \b invalid value. +*/ +wxJSONValue +wxJSONValue::ItemAt(unsigned index) const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + + wxJSONValue v(wxJSONTYPE_INVALID); + if (data->m_type == wxJSONTYPE_ARRAY) { + int size = Size(); + wxJSON_ASSERT(size >= 0); + if (index < (unsigned)size) { + v = data->m_valArray.Item(index); + } + } + return v; +} + +//! Return the item at the specified key. +/*! + The function returns a copy of the object in the map + that has the specified key. + If \c key does not exist, an \b invalid value is returned. +*/ +wxJSONValue +wxJSONValue::ItemAt(const wxString& key) const +{ + wxLogTrace(traceMask, _T("(%s) searched key=\'%s\'"), __PRETTY_FUNCTION__, key.c_str()); + wxLogTrace(traceMask, _T("(%s) actual object: %s"), __PRETTY_FUNCTION__, GetInfo().c_str()); + + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + + wxJSONValue v(wxJSONTYPE_INVALID); + if (data->m_type == wxJSONTYPE_OBJECT) { + wxJSONInternalMap::const_iterator it = data->m_valMap.find(key); + if (it != data->m_valMap.end()) { + v = it->second; + } + } + return v; +} + + +//! Return the item at the specified index. +/*! + The function returns a reference to the object at the specified + index. + If the element does not exist, the array is enlarged to \c index + 1 + elements and a reference to the last element will be returned. + New elements will contain NULL values. + If this object does not contain an array, the old value is + replaced by an array object. +*/ +wxJSONValue& +wxJSONValue::operator [] (unsigned index) +{ + wxJSONValue& v = Item(index); + return v; +} + +//! Return the item at the specified key. +/*! + The function returns a reference to the object in the map + that has the specified key. + If \c key does not exist, a new NULL value is created with + the provided key and a reference to it is returned. + If this object does not contain a map, the old value is + replaced by a map object. +*/ +wxJSONValue& +wxJSONValue::operator [] (const wxString& key) +{ + wxJSONValue& v = Item(key); + return v; +} + +// +// assignment operators +// note that reference counting is only used if the original +// value is a wxJSONValue object +// in all other cases, the operator= function deletes the old +// content and assigns the new one + + +//! Assign the specified value to this object replacing the old value. +/*! + The assignment operator assigns to this object the value specified in the + right operand of the assignment operator. + Note that the old value is deleted but not the other data members + in the wxJSONRefData structure. + This is particularly usefull for the parser class which stores + comment lines in a temporary wxJSONvalue object that is of type + wxJSONTYPE_INVALID. + As comment lines may apear before the value they refer to, comments + are stored in a value that is not yet being read. + when the value is read, it is assigned to the temporary JSON value + object without deleting the comment lines. +*/ +wxJSONValue& +wxJSONValue::operator = (int i) +{ + wxJSONRefData* data = SetType(wxJSONTYPE_INT); + data->m_value.VAL_INT = i; + return *this; +} + + +//! \overload operator = (int) +wxJSONValue& +wxJSONValue::operator = (bool b) +{ + wxJSONRefData* data = SetType(wxJSONTYPE_BOOL); + data->m_value.m_valBool = b; + return *this; +} + +//! \overload operator = (int) +wxJSONValue& +wxJSONValue::operator = (unsigned int ui) +{ + wxJSONRefData* data = SetType(wxJSONTYPE_UINT); + data->m_value.VAL_UINT = ui; + return *this; +} + +//! \overload operator = (int) +wxJSONValue& +wxJSONValue::operator = (long l) +{ + wxJSONRefData* data = SetType(wxJSONTYPE_INT); + data->m_value.VAL_INT = l; + return *this; +} + +//! \overload operator = (int) +wxJSONValue& +wxJSONValue::operator = (unsigned long ul) +{ + wxJSONRefData* data = SetType(wxJSONTYPE_UINT); + data->m_value.VAL_UINT = ul; + return *this; +} + + +//! \overload operator = (int) +wxJSONValue& +wxJSONValue::operator = (short i) +{ + wxJSONRefData* data = SetType(wxJSONTYPE_INT); + data->m_value.VAL_INT = i; + return *this; +} + + +//! \overload operator = (int) +wxJSONValue& +wxJSONValue::operator = (unsigned short ui) +{ + wxJSONRefData* data = SetType(wxJSONTYPE_UINT); + data->m_value.VAL_UINT = ui; + return *this; +} + +//! \overload operator = (int) +wxJSONValue& +wxJSONValue::operator = (double d) +{ + wxJSONRefData* data = SetType(wxJSONTYPE_DOUBLE); + data->m_value.m_valDouble = d; + return *this; +} + + +//! \overload operator = (int) +wxJSONValue& +wxJSONValue::operator = (const wxChar* str) +{ + wxJSONRefData* data = SetType(wxJSONTYPE_CSTRING); + data->m_value.m_valCString = str; +#if !defined( WXJSON_USE_CSTRING ) + data->m_type = wxJSONTYPE_STRING; + data->m_valString.assign(str); +#endif + return *this; +} + +//! \overload operator = (int) +wxJSONValue& +wxJSONValue::operator = (const wxString& str) +{ + wxJSONRefData* data = SetType(wxJSONTYPE_STRING); + data->m_valString.assign(str); + return *this; +} + + +//! Assigns to this object a memory buffer type +/*! + As with the ctor, this function makes a deep copy of the + memory buffer \c buff so changes made to the original buffer + does not reflect to the memory buffer stored in this JSON value. +*/ +wxJSONValue& +wxJSONValue::operator = (const wxMemoryBuffer& buff) +{ + wxJSONRefData* data = SetType(wxJSONTYPE_MEMORYBUFF); + data->m_memBuff = new wxMemoryBuffer(); + const void* ptr = buff.GetData(); + size_t len = buff.GetDataLen(); + if (data->m_memBuff && len) { + data->m_memBuff->AppendData(ptr, len); + } + return *this; +} + + +//! Assignment operator using reference counting. +/*! + Unlike all other assignment operators, this one makes a + swallow copy of the other JSON value object. + The function calls \c Ref() to get a shared referenced + data. + \sa \ref json_internals_cow +*/ +wxJSONValue& +wxJSONValue::operator = (const wxJSONValue& other) +{ + Ref(other); + return *this; +} + + +// finding elements + + +//! Return a value or a default value. +/*! + This function returns a copy of the value object for the specified key. + If the key is not found, a copy of \c defaultValue is returned. + Note that the returned values are not real copy of the \c key or the + default values because \e copy-on-write is used by this class. + However, you have to treat them as real copies; in other words, if you + change the values of the returned object your changes does not reflect + in the otiginal value. + Example: + \code + wxJSONValue defaultValue( 0 ); + wxJSONvalue v1; + v1["key"] = 100; // 'v1["key"]' contains the integer 100 + + // 'v2' contains 100 but it is a swallow copy of 'v1["key"]' + wxJSONValue v2 = v1.Get( "key", defaultValue ); + + // 'v1["key"]' still contains 100 + v2 = 200; + + // if you want your change to be reflected in the 'v1' object + // you have to assign it + v1["key"] = v2; + \endcode +*/ +wxJSONValue +wxJSONValue::Get(const wxString& key, const wxJSONValue& defaultValue) const +{ + // NOTE: this function does many wxJSONValue copies. + // so implementing COW is a good thing + + // this is the first copy (the default value) + wxJSONValue v(defaultValue); + + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + if (data->m_type == wxJSONTYPE_OBJECT) { + wxJSONInternalMap::iterator it = data->m_valMap.find(key); + if (it != data->m_valMap.end()) { + v = it->second; + } + } + return v; +} + + +// protected functions + +//! Find an element +/*! + The function returns a pointer to the element at index \c index + or a NULL pointer if \c index does not exist. + A NULL pointer is also returned if the object does not contain an + array nor a key/value map. +*/ +wxJSONValue* +wxJSONValue::Find(unsigned index) const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + + wxJSONValue* vp = 0; + + if (data->m_type == wxJSONTYPE_ARRAY) { + size_t size = data->m_valArray.GetCount(); + if (index < size) { + vp = &(data->m_valArray.Item(index)); + } + } + return vp; +} + +//! Find an element +/*! + The function returns a pointer to the element with key \c key + or a NULL pointer if \c key does not exist. + A NULL pointer is also returned if the object does not contain a + key/value map. +*/ +wxJSONValue* +wxJSONValue::Find(const wxString& key) const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + + wxJSONValue* vp = 0; + + if (data->m_type == wxJSONTYPE_OBJECT) { + wxJSONInternalMap::iterator it = data->m_valMap.find(key); + if (it != data->m_valMap.end()) { + vp = &(it->second); + } + } + return vp; +} + + + +//! Return a string description of the type +/*! + This static function is only usefull for debugging purposes and + should not be used by users of this class. + It simply returns a string representation of the JSON value + type stored in a object. + For example, if \c type is wxJSONTYPE_INT the function returns the + string "wxJSONTYPE_INT". + If \c type is out of range, an empty string is returned (should + never happen). +*/ +wxString +wxJSONValue::TypeToString(wxJSONType type) +{ + static const wxChar* str[] = { + _T("wxJSONTYPE_INVALID"), // 0 + _T("wxJSONTYPE_NULL"), // 1 + _T("wxJSONTYPE_INT"), // 2 + _T("wxJSONTYPE_UINT"), // 3 + _T("wxJSONTYPE_DOUBLE"), // 4 + _T("wxJSONTYPE_STRING"), // 5 + _T("wxJSONTYPE_CSTRING"), // 6 + _T("wxJSONTYPE_BOOL"), // 7 + _T("wxJSONTYPE_ARRAY"), // 8 + _T("wxJSONTYPE_OBJECT"), // 9 + _T("wxJSONTYPE_LONG"), // 10 + _T("wxJSONTYPE_INT64"), // 11 + _T("wxJSONTYPE_ULONG"), // 12 + _T("wxJSONTYPE_UINT64"), // 13 + _T("wxJSONTYPE_SHORT"), // 14 + _T("wxJSONTYPE_USHORT"), // 15 + _T("wxJSONTYPE_MEMORYBUFF"), // 16 + }; + + wxString s; + int idx = (int)type; + if (idx >= 0 && idx < 17) { + s = str[idx]; + } + return s; +} + +//! Returns informations about the object +/*! + The function is only usefull for debugging purposes and will probably + be dropped in future versions. + Returns a string that contains info about the object such as: + + \li the type of the object + \li the size + \li the progressive counter + \li the pointer to referenced data + \li the progressive counter of referenced data + \li the number of share of referenced data + +The \c deep parameter is used to specify if the function will be called +recursively in order to dump sub-items. If the parameter is TRUE than a +deep dump is executed. + +The \c indent is the initial indentation: it is incremented by 3 every +time the Dump() function is called recursively. +*/ +wxString +wxJSONValue::Dump(bool deep, int indent) const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + + wxJSONType type = GetType(); + + wxString s; + if (indent > 0) { + s.append(indent, ' '); + } + + wxString s1; + wxString s2; +#if defined( WXJSON_USE_VALUE_COUNTER ) + s1.Printf(_T("Object: Progr=%d Type=%s Size=%d comments=%d\n"), + m_progr, + TypeToString(type).c_str(), + Size(), + data->m_comments.GetCount()); + s2.Printf(_T(" : RefData=%p Progr=%d Num shares=%d\n"), + data, data->m_progr, data->GetRefCount()); +#else + s1.Printf(_T("Object: Type=%s Size=%d comments=%d\n"), + TypeToString(type).c_str(), + Size(), + data->m_comments.GetCount()); + s2.Printf(_T(" : RefData=%p Num shares=%d\n"), + data, data->GetRefCount()); +#endif + s.append(s1); + if (indent > 0) { + s.append(indent, ' '); + } + s.append(s2); + + wxString sub; + + // if we have to do a deep dump, we call the Dump() function for + // every sub-item + if (deep) { + indent += 3; + const wxJSONInternalMap* map; + int size;; + wxJSONInternalMap::const_iterator it; + switch (type) { + case wxJSONTYPE_OBJECT: + map = AsMap(); + size = Size(); + for (it = map->begin(); it != map->end(); ++it) { + const wxJSONValue& v = it->second; + sub = v.Dump(true, indent); + s.append(sub); + } + break; + case wxJSONTYPE_ARRAY: + size = Size(); + for (int i = 0; i < size; i++) { + const wxJSONValue* v = Find(i); + wxJSON_ASSERT(v); + sub = v->Dump(true, indent); + s.append(sub); + } + break; + default: + break; + } + } + return s; +} + +//! Returns informations about the object +/*! + The function is only usefull for debugging purposes and will probably + be dropped in future versions. + You should not rely on this function to exist in future versions. +*/ +wxString +wxJSONValue::GetInfo() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + + wxString s("Object:"); +#if defined( WXJSON_USE_VALUE_COUNTER ) + s << " Progr=" << data->m_progr; +#endif + s << " Type=" << wxJSONValue::TypeToString(data->m_type) + << " Size=" << Size() << " comments=" << data->m_comments.GetCount() + << '\n'; + + if (data->m_type == wxJSONTYPE_OBJECT) { + wxArrayString arr = GetMemberNames(); + for (unsigned int i = 0; i < arr.size(); i++) { + s.append(_T(" Member name: ")); + s.append(arr[i]); + s.append(_T("\n")); + } + } + return s; +} + +//! The comparison function +/*! + This function returns TRUE if this object looks like \c other. + Note that this class does not define a comparison operator + (the classical \b operator== function) because the notion + of \b equal for JSON values objects is not applicable. + The comment strings array are not compared: JSON value objects + are \b the \b same if they contains the same values, regardless the + comment's strings. + + Note that the function does not return the element that cause the + comparison to return FALSE. There is not a good structure to + tell this information. + If you need it for debugging purposes, you have to turn on the + \b sameas tracing feature by setting the WXTRACE environment + variable (you need a debug version of the application): + + \code + export WXTRACE=sameas // for unix systems that use bash + \endcode + + Note that if the two JSON value objects share the same referenced + data, the function immediatly returns TRUE without doing a deep + comparison which is, sure, useless. + For further info see \ref json_internals_compare. +*/ +bool +wxJSONValue::IsSameAs(const wxJSONValue& other) const +{ + // this is a recursive function: it calls itself + // for every 'value' object in an array or map + bool r = false; + + // some variables used in the switch statement + int size; + wxJSONInternalMap::const_iterator it; + + // get the referenced data for the two objects + wxJSONRefData* data = GetRefData(); + wxJSONRefData* otherData = other.GetRefData(); + + if (data == otherData) { + wxLogTrace(compareTraceMask, _T("(%s) objects share the same referenced data - r=TRUE"), + __PRETTY_FUNCTION__); + return true; + } + + + // if the type does not match the function compares the values if + // they are of compatible types such as INT, UINT and DOUBLE + if (data->m_type != otherData->m_type) { + // if the types are not compatible, returns false + // otherwise compares the compatible types: INT, UINT and DOUBLE + double val; + switch (data->m_type) { + case wxJSONTYPE_INT: + if (otherData->m_type == wxJSONTYPE_UINT) { + // compare the bits and returns true if value is between 0 and LLONG_MAX + if ((data->m_value.VAL_UINT <= LLONG_MAX) && + (data->m_value.VAL_UINT == otherData->m_value.VAL_UINT)) + { + r = true; + } + } + else if (otherData->m_type == wxJSONTYPE_DOUBLE) { + val = data->m_value.VAL_INT; + if (val == otherData->m_value.m_valDouble) { + r = true; + } + } + else { + r = false; + } + break; + case wxJSONTYPE_UINT: + if (otherData->m_type == wxJSONTYPE_INT) { + // compare the bits and returns true if value is between 0 and LLONG_MAX + if ((data->m_value.VAL_UINT <= LLONG_MAX) && + (data->m_value.VAL_UINT == otherData->m_value.VAL_UINT)) + { + r = true; + } + } + else if (otherData->m_type == wxJSONTYPE_DOUBLE) { + val = data->m_value.VAL_UINT; + if (val == otherData->m_value.m_valDouble) { + r = true; + } + } + else { + r = false; + } + break; + case wxJSONTYPE_DOUBLE: + if (otherData->m_type == wxJSONTYPE_INT) { + val = otherData->m_value.VAL_INT; + if (val == data->m_value.m_valDouble) { + r = true; + } + } + else if (otherData->m_type == wxJSONTYPE_UINT) { + val = otherData->m_value.VAL_UINT; + if (val == data->m_value.m_valDouble) { + r = true; + } + } + else { + r = false; + } + break; + default: + r = false; + break; + } + return r; + } + + // the two objects have the same 'm_type' + + // for comparing wxJSONTYPE_CSTRING we use two temporary wxString + // objects: this is to avoid using strcmp() and wcscmp() which + // may not be available on all platforms + wxString s1, s2; + r = true; + int r1; + + switch (data->m_type) { + case wxJSONTYPE_INVALID: + case wxJSONTYPE_NULL: + // there is no need to compare the values + break; + case wxJSONTYPE_INT: + if (data->m_value.VAL_INT != otherData->m_value.VAL_INT) { + r = false; + } + break; + case wxJSONTYPE_UINT: + if (data->m_value.VAL_UINT != otherData->m_value.VAL_UINT) { + r = false; + } + break; + case wxJSONTYPE_DOUBLE: + if (data->m_value.m_valDouble != otherData->m_value.m_valDouble) { + r = false; + } + break; + case wxJSONTYPE_CSTRING: + s1 = wxString(data->m_value.m_valCString); + s2 = wxString(otherData->m_value.m_valCString); + if (s1 != s2) { + r = false; + } + break; + case wxJSONTYPE_BOOL: + if (data->m_value.m_valBool != otherData->m_value.m_valBool) { + r = false; + } + break; + case wxJSONTYPE_STRING: + if (data->m_valString != otherData->m_valString) { + r = false; + } + break; + case wxJSONTYPE_MEMORYBUFF: + // we cannot simply use the operator ==; we need a deep comparison + r1 = CompareMemoryBuff(*(data->m_memBuff), *(otherData->m_memBuff)); + if (r1 != 0) { + r = false; + } + break; + case wxJSONTYPE_ARRAY: + size = Size(); + wxLogTrace(compareTraceMask, _T("(%s) Comparing an array object - size=%d"), + __PRETTY_FUNCTION__, size); + + if (size != other.Size()) { + wxLogTrace(compareTraceMask, _T("(%s) Sizes does not match"), + __PRETTY_FUNCTION__); + return false; + } + // compares every element in this object with the element of + // the same index in the 'other' object + for (int i = 0; i < size; i++) { + wxLogTrace(compareTraceMask, _T("(%s) Comparing array element=%d"), + __PRETTY_FUNCTION__, i); + wxJSONValue v1 = ItemAt(i); + wxJSONValue v2 = other.ItemAt(i); + + if (!v1.IsSameAs(v2)) { + return false; + } + } + break; + case wxJSONTYPE_OBJECT: + size = Size(); + wxLogTrace(compareTraceMask, _T("(%s) Comparing a map obejct - size=%d"), + __PRETTY_FUNCTION__, size); + + if (size != other.Size()) { + wxLogTrace(compareTraceMask, _T("(%s) Comparison failed - sizes does not match"), + __PRETTY_FUNCTION__); + return false; + } + // for every key calls itself on the value found in + // the other object. if 'key' does no exist, returns FALSE + for (it = data->m_valMap.begin(); it != data->m_valMap.end(); it++) { + wxString key = it->first; + wxLogTrace(compareTraceMask, _T("(%s) Comparing map object - key=%s"), + __PRETTY_FUNCTION__, key.c_str()); + wxJSONValue otherVal = other.ItemAt(key); + bool isSame = it->second.IsSameAs(otherVal); + if (!isSame) { + wxLogTrace(compareTraceMask, _T("(%s) Comparison failed for the last object"), + __PRETTY_FUNCTION__); + return false; + } + } + break; + default: + // should never happen + wxFAIL_MSG(_T("wxJSONValue::IsSameAs() unexpected wxJSONType")); + break; + } + return r; +} + +//! Add a comment to this JSON value object. +/*! + The function adds a comment string to this JSON value object and returns + the total number of comment strings belonging to this value. + Note that the comment string must be a valid C/C++ comment because the + wxJSONWriter does not modify it. + In other words, a C++ comment string must start with '//' and must end with + a new-line character. If the final LF char is missing, the + automatically adds it. + You can also add C-style comments which must be enclosed in the usual + C-comment characters. + For C-style comments, the function does not try to append the final comment + characters but allows trailing whitespaces and new-line chars. + The \c position parameter is one of: + + \li wxJSONVALUE_COMMENT_BEFORE: the comment will be written before the value + \li wxJSONVALUE_COMMENT_INLINE: the comment will be written on the same line + \li wxJSONVALUE_COMMENT_AFTER: the comment will be written after the value + \li wxJSONVALUE_COMMENT_DEFAULT: the old value of comment's position is not + changed; if no comments were added to the value object this is the + same as wxJSONVALUE_COMMENT_BEFORE. + + To know more about comment's storage see \ref json_comment_add + +*/ +int +wxJSONValue::AddComment(const wxString& str, int position) +{ + wxJSONRefData* data = COW(); + wxJSON_ASSERT(data); + + wxLogTrace(traceMask, _T("(%s) comment=%s"), __PRETTY_FUNCTION__, str.c_str()); + int r = -1; + int len = str.length(); + if (len < 2) { + wxLogTrace(traceMask, _T(" error: len < 2")); + return -1; + } + if (str[0] != '/') { + wxLogTrace(traceMask, _T(" error: does not start with\'/\'")); + return -1; + } + if (str[1] == '/') { // a C++ comment: check that it ends with '\n' + wxLogTrace(traceMask, _T(" C++ comment")); + if (str.GetChar(len - 1) != '\n') { + wxString temp(str); + temp.append(1, '\n'); + data->m_comments.Add(temp); + wxLogTrace(traceMask, _T(" C++ comment: LF added")); + } + else { + data->m_comments.Add(str); + } + r = data->m_comments.size(); + } + else if (str[1] == '*') { // a C-style comment: check that it ends with '*/' + wxLogTrace(traceMask, _T(" C-style comment")); + int lastPos = len - 1; + wxChar ch = str.GetChar(lastPos); + // skip leading whitespaces + while (ch == ' ' || ch == '\n' || ch == '\t') { + --lastPos; + ch = str.GetChar(lastPos); + } + if (str.GetChar(lastPos) == '/' && str.GetChar(lastPos - 1) == '*') { + data->m_comments.Add(str); + r = data->m_comments.size(); + } + } + else { + wxLogTrace(traceMask, _T(" error: is not a valid comment string")); + r = -1; + } + // if the comment was stored, store the position + if (r >= 0 && position != wxJSONVALUE_COMMENT_DEFAULT) { + data->m_commentPos = position; + } + return r; +} + +//! Add one or more comments to this JSON value object. +/*! + The function adds the strings contained in \c comments to the comment's + string array of this value object by calling the AddComment( const wxString&,int) + function for every string in the \c comment array. + Returns the number of strings correctly added. +*/ +int +wxJSONValue::AddComment(const wxArrayString& comments, int position) +{ + int siz = comments.GetCount(); int r = 0; + for (int i = 0; i < siz; i++) { + int r2 = AddComment(comments[i], position); + if (r2 >= 0) { + ++r; + } + } + return r; +} + +//! Return a comment string. +/*! + The function returns the comment string at index \c idx. + If \c idx is out of range, an empty string is returned. + If \c idx is equal to -1, then the function returns a string + that contains all comment's strings stored in the array. +*/ +wxString +wxJSONValue::GetComment(int idx) const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + + wxString s; + int size = data->m_comments.GetCount(); + if (idx < 0) { + for (int i = 0; i < size; i++) { + s.append(data->m_comments[i]); + } + } + else if (idx < size) { + s = data->m_comments[idx]; + } + return s; +} + +//! Return the number of comment strings. +int +wxJSONValue::GetCommentCount() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + + int d = data->m_comments.GetCount(); + wxLogTrace(traceMask, _T("(%s) comment count=%d"), __PRETTY_FUNCTION__, d); + return d; +} + +//! Return the comment position. +int +wxJSONValue::GetCommentPos() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + return data->m_commentPos; +} + +//! Get the comment string's array. +const wxArrayString& +wxJSONValue::GetCommentArray() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + + return data->m_comments; +} + +//! Clear all comment strings +void +wxJSONValue::ClearComments() +{ + wxJSONRefData* data = COW(); + wxJSON_ASSERT(data); + + data->m_comments.clear(); +} + + +//! Set the type of the stored value. +/*! + The function sets the type of the stored value as specified in + the provided argument. + If the actual type is equal to \c type, nothing happens and this + JSON value object retains the original type and value. + If the type differs, however, the original type and value are + lost. + + The function just sets the type of the object and not the + value itself. + If the object does not have a data structure it is allocated + using the CreateRefData() function unless the type to be set + is wxJSONTYPE_INVALID. In this case and if a data structure is + not yet allocated, it is not allocated. + + If the object already contains a data structure it is not deleted + but the type is changed in the original data structure. + Complex values in the old structure are cleared. + The \c type argument can be one of the following: + + \li wxJSONTYPE_INVALID: an empty (not initialized) JSON value + \li wxJSONTYPE_NULL: a NULL value + \li wxJSONTYPE_INT: an integer value + \li wxJSONTYPE_UINT: an unsigned integer + \li wxJSONTYPE_DOUBLE: a double precision number + \li wxJSONTYPE_BOOL: a boolean + \li wxJSONTYPE_CSTRING: a C string + \li wxJSONTYPE_STRING: a wxString object + \li wxJSONTYPE_ARRAY: an array of wxJSONValue objects + \li wxJSONTYPE_OBJECT: a hashmap of key/value pairs where \e value is a wxJSONValue object + \li wxJSONTYPE_LONG: a 32-bits integer value + \li wxJSONTYPE_ULONG: an unsigned 32-bits integer + \li wxJSONTYPE_INT64: a 64-bits integer value + \li wxJSONTYPE_UINT64: an unsigned 64-bits integer + \li wxJSONTYPE_SHORT: a signed short integer + \li wxJSONTYPE_USHORT: an unsigned short integer + \li wxJSONTYPE_MEMORYBUFF: a binary memory buffer + + The integer storage depends on the platform: for platforms that support 64-bits + integers, integers are always stored as 64-bits integers. + On platforms that do not support 64-bits integers, ints are stored as \b long \b int. + To know more about the internal representation of integers, read + \ref json_internals_integer. + + Note that there is no need to set a type for the object in order to assign + a value to it. + In other words, if you want to construct a JSON value which holds an integer + value of 10, just use the specific constructor: + \code + wxJSONValue value( 10 ); + \endcode + which sets the integer type and also the numeric value. + Moreover, there is no need to set the type for none of the handled types, + not only for primitive types but for complex types, too. + For example, if you want to construct an array of JSON values, just use + the default ctor and call the Append() member function which will append the + first element to the array and will set the array type: + \code + wxJSONValue value; + value.Append( "a string" ); + \endcode + \sa GetType +*/ +wxJSONRefData* +wxJSONValue::SetType(wxJSONType type) +{ + wxJSONRefData* data = GetRefData(); + wxJSONType oldType = GetType(); + + // check that type is within the allowed range + wxJSON_ASSERT((type >= wxJSONTYPE_INVALID) && (type <= wxJSONTYPE_MEMORYBUFF)); + if ((type < wxJSONTYPE_INVALID) || (type > wxJSONTYPE_MEMORYBUFF)) { + type = wxJSONTYPE_INVALID; + } + + // the function unshares the referenced data but does not delete the + // structure. This is because the wxJSON reader stores comments + // that apear before the value in a temporary value of type wxJSONTYPE_INVALID + // which is invalid and, next, it stores the JSON value in the same + // wxJSONValue object. + // If we would delete the structure using 'Unref()' we loose the + // comments + data = COW(); + + // do nothing if the actual type is the same as 'type' + if (type == oldType) { + return data; + } + + // change the type of the referened structure + // NOTE: integer types are always stored as the generic integer types + if (type == wxJSONTYPE_LONG || type == wxJSONTYPE_INT64 || type == wxJSONTYPE_SHORT) { + type = wxJSONTYPE_INT; + } + if (type == wxJSONTYPE_ULONG || type == wxJSONTYPE_UINT64 || type == wxJSONTYPE_USHORT) { + type = wxJSONTYPE_UINT; + } + + wxJSON_ASSERT(data); + data->m_type = type; + + // clears complex objects of the old type + switch (oldType) { + case wxJSONTYPE_STRING: + data->m_valString.clear(); + break; + case wxJSONTYPE_ARRAY: + data->m_valArray.Clear(); + break; + case wxJSONTYPE_OBJECT: + data->m_valMap.clear(); + break; + case wxJSONTYPE_MEMORYBUFF: + // we first have to delete the actual memory buffer, if any + if (data->m_memBuff) { + delete data->m_memBuff; + data->m_memBuff = 0; + } + break; + default: + // there is not need to clear primitive types + break; + } + + // if the WXJSON_USE_CSTRING macro is not defined, the class forces + // C-string to be stored as wxString objects +#if !defined( WXJSON_USE_CSTRING ) + if (data->m_type == wxJSONTYPE_CSTRING) { + data->m_type = wxJSONTYPE_STRING; + } +#endif + return data; +} + +//! Return the line number of this JSON value object +/*! + The line number of a JSON value object is set to -1 when the + object is constructed. + The line number is set by the parser class, wxJSONReader, when + a JSON text is read from a stream or a string. + it is used when reading a comment line: comment lines that apear + on the same line as a value are considered \b inline comments of + the value. +*/ +int +wxJSONValue::GetLineNo() const +{ + // return ZERO if there is not a referenced data structure + int n = 0; + wxJSONRefData* data = GetRefData(); + if (data != 0) { + n = data->m_lineNo; + } + return n; +} + +//! Set the line number of this JSON value object. +void +wxJSONValue::SetLineNo(int num) +{ + wxJSONRefData* data = COW(); + wxJSON_ASSERT(data); + data->m_lineNo = num; +} + +//! Set the pointer to the referenced data. +void +wxJSONValue::SetRefData(wxJSONRefData* data) +{ + m_refData = data; +} + +//! Increments the referenced data counter. +void +wxJSONValue::Ref(const wxJSONValue& clone) +{ + // nothing to be done + if (m_refData == clone.m_refData) + return; + + // delete reference to old data + UnRef(); + + // reference new data + if (clone.m_refData) { + m_refData = clone.m_refData; + ++(m_refData->m_refCount); + } +} + +//! Unreferences the shared data +/*! + The function decrements the number of shares in wxJSONRefData::m_refCount + and if it is ZERO, deletes the referenced data. + It is called by the destructor and by the copy-on-write functions. +*/ +void +wxJSONValue::UnRef() +{ + if (m_refData) { + wxASSERT_MSG(m_refData->m_refCount > 0, _T("invalid ref data count")); + + if (--m_refData->m_refCount == 0) { + delete m_refData; + m_refData = NULL; + } + } +} + +//! Makes an exclusive copy of shared data +void +wxJSONValue::UnShare() +{ + AllocExclusive(); +} + + +//! Do a deep copy of the other object. +/*! + This function allocates a new ref-data structure and copies it + from the object \c other. +*/ +void +wxJSONValue::DeepCopy(const wxJSONValue& other) +{ + UnRef(); + wxJSONRefData* data = CloneRefData(other.m_refData); + SetRefData(data); +} + +//! Return the pointer to the referenced data structure. +wxJSONRefData* +wxJSONValue::GetRefData() const +{ + wxJSONRefData* data = m_refData; + return data; +} + + +//! Make a copy of the referenced data. +/*! + The function allocates a new instance of the wxJSONRefData + structure, copies the content of \c other and returns the pointer + to the newly created structure. + This function is called by the wxObject::UnRef() function + when a non-const member function is called on multiple + referenced data. +*/ +wxJSONRefData* +wxJSONValue::CloneRefData(const wxJSONRefData* otherData) const +{ + wxJSON_ASSERT(otherData); + + // make a static cast to pointer-to-wxJSONRefData + const wxJSONRefData* other = otherData; + + // allocate a new instance of wxJSONRefData using the default + // ctor; we cannot use the copy ctor of a wxJSONRefData + wxJSONRefData* data = new wxJSONRefData(); + + // copy the referenced data structure's data members + data->m_type = other->m_type; + data->m_value = other->m_value; + data->m_commentPos = other->m_commentPos; + data->m_comments = other->m_comments; + data->m_lineNo = other->m_lineNo; + data->m_valString = other->m_valString; + data->m_valArray = other->m_valArray; + data->m_valMap = other->m_valMap; + + // if the data contains a wxMemoryBuffer object, then we have + // to make a deep copy of the buffer by allocating a new one because + // wxMemoryBuffer is not a copy-on-write structure + if (other->m_memBuff) { + data->m_memBuff = new wxMemoryBuffer(); + const void* ptr = data->m_memBuff->GetData(); + size_t len = data->m_memBuff->GetDataLen(); + if (data->m_memBuff && len) { + data->m_memBuff->AppendData(ptr, len); + } + } + + wxLogTrace(cowTraceMask, _T("(%s) CloneRefData() PROGR: other=%d data=%d"), + __PRETTY_FUNCTION__, other->GetRefCount(), data->GetRefCount()); + + return data; +} + +//! Create a new data structure +/*! + The function allocates a new instance of the wxJSONRefData + structure and returns its pointer. + The type of the JSON value is set to wxJSONTYPE_INVALID (= + a not initialized value). +*/ +wxJSONRefData* +wxJSONValue::CreateRefData() const +{ + wxJSONRefData* data = new wxJSONRefData(); + data->m_type = wxJSONTYPE_INVALID; + return data; +} + + + +//! Make sure the referenced data is unique +/*! + This function is called by all non-const member functions and makes + sure that the referenced data is unique by calling \b UnShare() + If the referenced data is shared acrosss other wxJSONValue instances, + the \c UnShare() function makes a private copy of the shared data. +*/ +wxJSONRefData* +wxJSONValue::COW() +{ + wxJSONRefData* data = GetRefData(); + wxLogTrace(cowTraceMask, _T("(%s) COW() START data=%p data->m_count=%d"), + __PRETTY_FUNCTION__, data, data->GetRefCount()); + UnShare(); + data = GetRefData(); + wxLogTrace(cowTraceMask, _T("(%s) COW() END data=%p data->m_count=%d"), + __PRETTY_FUNCTION__, data, data->GetRefCount()); + return GetRefData(); +} + +//! Makes a private copy of the referenced data +void +wxJSONValue::AllocExclusive() +{ + if (!m_refData) { + m_refData = CreateRefData(); + } + else if (m_refData->GetRefCount() > 1) { + // note that ref is not going to be destroyed in this case + const wxJSONRefData* ref = m_refData; + UnRef(); + + // ... so we can still access it + m_refData = CloneRefData(ref); + } + //else: ref count is 1, we are exclusive owners of m_refData anyhow + + wxASSERT_MSG(m_refData && m_refData->GetRefCount() == 1, + _T("wxObject::AllocExclusive() failed.")); +} + +//! Convert memory buffer object to a string representation. +/*/ + The fucntion returns a string representation of the data contained in the + memory buffer object \c buff. + The string is conposed of two hexadecimal digits for every byte contained + in the memory buffer; bytes are separated by a space character. + The string starts with the actual lenght of the data enclosed in parenthesis. + The string will contain \c len bytes if \c len is less than the length + of the actual data in \c buff. + Note that the (len) printed in the output referes to the length of the buffer + which may be greater than the length that has to be printed. + + \b Example: + This is an example of printing a memory buffer object that contains 10 bytes: + \code + 0x80974653 (10) 00 01 02 03 04 05 06 07 08 09 + \endcode +*/ +wxString +wxJSONValue::MemoryBuffToString(const wxMemoryBuffer& buff, size_t len) +{ + size_t buffLen = buff.GetDataLen(); + void* ptr = buff.GetData(); + wxString s = MemoryBuffToString(ptr, MIN(buffLen, len), buffLen); + return s; +} + + +//! Convert a binary memory buffer to a string representation. +/*/ + The function returns a string representation of the data contained in the + binary memory buffer pointed to by \c buff for \c len bytes. + The string is composed of two hexadecimal digits for every byte contained + in the memory buffer; bytes are separated by a space character. + The string starts with pointer to binary data followed by the lenght of the + data enclosed in parenthesis. + + \b Example: + This is an example of printing ten bytes from a memory buffer: + \code + 0x80974653 (10) 00 01 02 03 04 05 06 07 08 09 + \endcode + + @param buff the pointer to the memory buffer data + @len the length of the data that has to be printed + @actualLen the real lenght of the memory buffer that has to be printed + just afetr the pointer; may be greater than \c len. If this parameter + is -1 then it is equal to \c len +*/ +wxString +wxJSONValue::MemoryBuffToString(const void* buff, size_t len, size_t actualLen) +{ + wxString s; + size_t buffLen = actualLen; + if (buffLen == (size_t)-1) { + buffLen = len; + } + s.Printf(_T("%p (%u) "), buff, buffLen); + unsigned char* ptr = (unsigned char*)buff; + for (unsigned int i = 0; i < len; i++) { + unsigned char c = *ptr; + ++ptr; + // now convert the character + char c1 = c / 16; + char c2 = c % 16; + c1 += '0'; + c2 += '0'; + if (c1 > '9') { + c1 += 7; + } + if (c2 > '9') { + c2 += 7; + } + s.Append(c1, 1); + s.Append(c2, 1); + s.Append(' ', 1); // a space separates the bytes + } + return s; +} + +//! Compares two memory buffer objects +/*! + The function is the counterpart of the comparison operator == for two wxMemoryBuffer + objects. + You may noticed that the wxMemoryBuffer class does not define comparison operators so + if you write a code snippset like the following: + \code + wxMemoryBuffer b1; + wxMemoryBuffer b2; + b1.AppendData( "1234567890", 10 ); + b2.AppendData( "1234567890", 10 ); + bool r = b1 == b2; + \endcode + + you may expect that \b r is TRUE, because both objects contain the same data. + This is not true. The result you get is FALSE because the default comparison operator + is used, which just compares the data members of the class. + The data member is the pointer to the allocated memory that contains the data and + they are not equal. + This function uses the (fast) \b memcmp function to compare the actual data + contained in the nenory buffer objects thus doing a deep comparison. + The function returns the return value of \b memcmp: + + the memcmp() function returns an integer less than, equal to, or + greater than zero if the first n bytes of \c buff1 is found, respectively, to + be less than, to match, or be greater than the first n bytes of \c buff2. +*/ +int +wxJSONValue::CompareMemoryBuff(const wxMemoryBuffer& buff1, const wxMemoryBuffer& buff2) +{ + int r; + size_t buff1Len = buff1.GetDataLen(); + size_t buff2Len = buff2.GetDataLen(); + if (buff1Len > buff2Len) { + r = 1; + } + else if (buff1Len < buff2Len) { + r = -1; + } + else { + r = memcmp(buff1.GetData(), buff2.GetData(), buff1Len); + } + return r; +} + +//! Compares a memory buffer object and a memory buffer +/*! + The function compares the data contained in a memory buffer object with a + memory buffer. + This function uses the (fast) \b memcmp function to compare the actual data + contained in the nenory buffer object thus doing a deep comparison. + The function returns the return value of \b memcmp: + + The memcmp() function returns an integer less than, equal to, or + greater than zero if the first n bytes of \c buff1 is found, respectively, to + be less than, to match, or be greater than the first n bytes of \c buff2. +*/ +int +wxJSONValue::CompareMemoryBuff(const wxMemoryBuffer& buff1, const void* buff2) +{ + int r; + size_t buff1Len = buff1.GetDataLen(); + r = memcmp(buff1.GetData(), buff2, buff1Len); + return r; +} + + +//! Converts an array of INTs to a memory buffer +/*! + This static function converts an array of INTs stored in a wxJSONvalue object + into a memory buffer object. + The wxJSONvalue object passed as parameter must be of type ARRAY and must contain + INT types whose values are between 0 and 255. + + Every element of the array si converted to a BYTE value and appended to the returned + wxMemoryBuffer object. The following rules apply in the conversion: + \li if \c value is not an ARRAY type, an empty memory buffer is returned + \li if the \c value array contains elements of type other than INT, those + elements are ignored + \li if the \c value array contains elements of type INT which value is outside the + range 0..255, those elements are ignored + \li if the \c value array contains only ignored elements an empty wxMemoryBuffer + object is returned. + + This function can be used to get a memory buffer object from valid JSON text. + Please note that the wxJSONReader cannot know which array of INTs represent a binary + memory buffer unless you use the \b wxJSON \e memory \e buffer extension in the writer and + in the reader. +*/ +wxMemoryBuffer +wxJSONValue::ArrayToMemoryBuff(const wxJSONValue& value) +{ + wxMemoryBuffer buff; + if (value.IsArray()) { + int len = value.Size(); + for (int i = 0; i < len; i++) { + short int byte; unsigned char c; + // we do not use opertaor [] because it is not const + // bool r = value[i].AsShort( byte ); + bool r = value.ItemAt(i).AsShort(byte); + if (r && (byte >= 0 && byte <= 255)) { + c = (unsigned char)byte; + buff.AppendByte(c); + } + } + } + return buff; +} + + +/************************************************************************* + + 64-bits integer support + +*************************************************************************/ + +#if defined( wxJSON_64BIT_INT) + + +//! \overload wxJSONValue() +wxJSONValue::wxJSONValue(wxInt64 i) +{ + m_refData = 0; + wxJSONRefData* data = Init(wxJSONTYPE_INT); + wxJSON_ASSERT(data); + if (data != 0) { + data->m_value.VAL_INT = i; + } +} + +//! \overload wxJSONValue() +wxJSONValue::wxJSONValue(wxUint64 ui) +{ + m_refData = 0; + wxJSONRefData* data = Init(wxJSONTYPE_UINT); + wxJSON_ASSERT(data); + if (data != 0) { + data->m_value.VAL_UINT = ui; + } +} + +//! Return TRUE if the stored value is a 32-bits integer +/*! + This function is only available on 64-bits platforms and returns + TRUE if, and only if, the stored value is of type \b wxJSONTYPE_INT + and the numeric value fits in a 32-bits signed integer. + The function just calls IsLong() and returns the value returned by + that function. + The use of this function is deprecated: use \c IsLong() instead +*/ +bool +wxJSONValue::IsInt32() const +{ + bool r = IsLong(); + return r; +} + +//! Return TRUE if the stored value is a unsigned 32-bits integer +/*! + This function is only available on 64-bits platforms and returns + TRUE if, and only if, the stored value is of type \b wxJSONTYPE_UINT + and the numeric value fits in a 32-bits unsigned integer. + The function just calls IsULong() and returns the value returned by + that function. + The use of this function is deprecated: use \c IsULong() instead +*/ +bool +wxJSONValue::IsUInt32() const +{ + bool r = IsULong(); + return r; +} + + +//! Return TRUE if the stored value is integer. +/*! + This function returns TRUE if the stored value is of + type signed integer. + In other words, the function returns TRUE if the \c wxJSONRefData::m_type + data member is of type \c wxJSONTYPE_INT + The function is only available if 64-bits integer support is enabled. + + \sa \ref json_internals_integer +*/ +bool +wxJSONValue::IsInt64() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + bool r = false; + if (data->m_type == wxJSONTYPE_INT) { + r = true; + } + return r; +} + + +//! Return TRUE if the stored value is a unsigned integer +/*! + This function returns TRUE if the stored value is of + type unsigned integer. + In other words, the function returns TRUE if the \c wxJSONRefData::m_type + data member is of type \c wxJSONTYPE_UINT. + The function is only available if 64-bits integer support is enabled. + + \sa \ref json_internals_integer +*/ +bool +wxJSONValue::IsUInt64() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + bool r = false; + if (data->m_type == wxJSONTYPE_UINT) { + r = true; + } + return r; +} + +//! Returns the low-order 32 bits of the value as an integer +/*! + This function is only available on 64-bits platforms and returns + the low-order 32-bits of the integer stored in the JSON value. + Note that all integer types are stored as \b wx(U)Int64 data types by + the JSON value class and that the function does not check that the + numeric value fits in a 32-bit integer. + The function just calls AsLong() and casts the value in a wxInt32 data + type + + \sa \ref wxjson_tutorial_get +*/ +wxInt32 +wxJSONValue::AsInt32() const +{ + wxInt32 i; + i = (wxInt32)AsLong(); + return i; +} + +//! Returns the low-order 32 bits of the value as an unsigned integer +/*! + This function is only available on 64-bits platforms and returns + the low-order 32-bits of the integer stored in the JSON value. + Note that all integer types are stored as \b wx(U)Int64 data types by + the JSON value class and that the function does not check that the + numeric value fits in a 32-bit integer. + The function just calls AsULong() and casts the value in a wxUInt32 data + type + + \sa \ref wxjson_tutorial_get +*/ +wxUint32 +wxJSONValue::AsUInt32() const +{ + wxUint32 ui; + ui = (wxUint32)AsULong(); + return ui; +} + + +//! Return the numeric value as a 64-bit integer. +/*! + This function is only available on 64-bits platforms and returns + the numeric value as a 64-bit integer. + + Note that the function does not check that the type of the + value is actually an integer and it just returns the content + of the wxJSONValueHolder union. + However, in debug builds, the function ASSERTs that the + type of the stored value is wxJSONTYPE_INT. + + \sa \ref json_internals_integer + \sa \ref wxjson_tutorial_get +*/ +wxInt64 +wxJSONValue::AsInt64() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + wxInt64 i64 = data->m_value.m_valInt64; + + wxJSON_ASSERT(IsInt64()); // exapnds only in debug builds + return i64; +} + +//! Return the numeric value as a 64-bit unsigned integer. +/*! + This function is only available on 64-bits platforms and returns + the numeric value as a 64-bit unsigned integer. + + Note that the function does not check that the type of the + value is actually an integer and it just returns the content + of the wxJSONValueHolder union. + However, in debug builds, the function wxJSON_ASSERTs that the + type of the stored value is wxJSONTYPE_UINT. + + \sa \ref json_internals_integer + \sa \ref wxjson_tutorial_get +*/ +wxUint64 +wxJSONValue::AsUInt64() const +{ + wxJSONRefData* data = GetRefData(); + wxJSON_ASSERT(data); + wxUint64 ui64 = data->m_value.m_valUInt64; + + wxJSON_ASSERT(IsUInt64()); // exapnds only in debug builds + return ui64; +} + +bool +wxJSONValue::AsInt32(wxInt32& i32) const +{ + bool r = IsInt32(); + if (r) { + i32 = AsInt32(); + } + return r; +} + +bool +wxJSONValue::AsUInt32(wxUint32& ui32) const +{ + bool r = IsUInt32(); + if (r) { + ui32 = AsUInt32(); + } + return r; +} + +bool +wxJSONValue::AsInt64(wxInt64& i64) const +{ + bool r = IsInt64(); + if (r) { + i64 = AsInt64(); + } + return r; +} + +bool +wxJSONValue::AsUInt64(wxUint64& ui64) const +{ + bool r = IsUInt64(); + if (r) { + ui64 = AsUInt64(); + } + return r; +} + +//! \overload Append( const wxJSONValue& ) +wxJSONValue& +wxJSONValue::Append(wxInt64 i) +{ + wxJSONValue v(i); + wxJSONValue& r = Append(v); + return r; +} + +//! \overload Append( const wxJSONValue& ) +wxJSONValue& +wxJSONValue::Append(wxUint64 ui) +{ + wxJSONValue v(ui); + wxJSONValue& r = Append(v); + return r; +} + + +//! \overload operator = (int) +wxJSONValue& +wxJSONValue::operator = (wxInt64 i) +{ + wxJSONRefData* data = SetType(wxJSONTYPE_INT); + data->m_value.VAL_INT = i; + return *this; +} + +//! \overload operator = (int) +wxJSONValue& +wxJSONValue::operator = (wxUint64 ui) +{ + wxJSONRefData* data = SetType(wxJSONTYPE_UINT); + data->m_value.VAL_UINT = ui; + return *this; +} + + +#endif // defined( wxJSON_64BIT_INT ) + + + + +/* +{ +} +*/ + diff --git a/utils/json/jsonwriter.cpp b/utils/json/jsonwriter.cpp new file mode 100644 index 0000000..776276a --- /dev/null +++ b/utils/json/jsonwriter.cpp @@ -0,0 +1,1276 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: jsonwriter.cpp +// Purpose: the wxJSONWriter class: a JSON text generator +// Author: Luciano Cattani +// Created: 2007/10/12 +// RCS-ID: $Id: jsonwriter.cpp,v 1.6 2008/03/03 19:05:47 luccat Exp $ +// Copyright: (c) 2007 Luciano Cattani +// Licence: wxWidgets licence +///////////////////////////////////////////////////////////////////////////// + +#ifdef __GNUG__ +#pragma implementation "jsonwriter.cpp" +#endif + + +#include "pgadmin3.h" + +#include +#include +#include +#include + +static const wxChar* writerTraceMask = _T("traceWriter"); + +/*! \class wxJSONWriter + \brief The JSON document writer + + This class is a JSON document writer and it is used to write a + wxJSONValue object to an output stream or to a string object. + The ctor accepts some parameters which can be used to + change the style of the output. + The default output is in human-readable format that uses a three-space + indentation for object / array sub-items and separates every + value with a linefeed character. + + + \par Examples + + Using the default writer constructor + + \code + // construct the JSON value object and add values to it + wxJSONValue root; + root["key1"] = "some value"; + ... + + // construct the string that will contain the JSON text + wxString str; + + // construct a JSON writer: use the default writer's settings + wxJSONWriter writer; + + // call the writer's Write() memberfunction + writer.Write( root, str ); + \endcode + + + To write a JSON value object using a four-spaces indentation and forcing all + comment strings to apear before the value they refer to, use the following code: + \code + wxJSONWriter writer( wxJSONWRITER_STYLED | // want a styled output + wxJSONWRITER_WRITE_COMMENTS | // want comments in the document + wxJSONWRITER_COMMENTS_BEFORE, // force comments before value + 0, // initial indentation + 4); // indentation step + writer.Write( value, document ); + \endcode + + The following code construct a JSON writer that produces the most compact + text output but it is hard to read by humans: + + \code + wxJSONWriter writer( wxJSONWRITER_NONE ); + writer.Write( value, document ); + \endcode + + + \par The two types of output objects + + You can write JSON text to two different kind of objects: + + \li a string object (\b wxString) + \li a stream object (\b wxOutputStream) + + When writing to a string object, the output is platform- and mode-dependent. + In ANSI builds, the JSON text output in the string object will + contain one-byte characters: the actual characters represented is + locale dependent. + In Unicode builds, the JSON text output in the string contains + wide characters which encoding format is platform dependent: UCS-2 in + Windows, UCS-4 in GNU/Linux. + Starting from wxWidgets version 2.9 the internal encoding for Unicode + builds in linux/unix systems is UTF-8. + + When writing to a stream object, the JSON text output is always + encoded in UTF-8 in both ANSI and Unicode builds. + In ANSI builds the user may want to suppress UTF-8 encoding so + that the JSON text can be stored in ANSI format. + Note that this is not valid JSON text unless all characters written + to the JSON text document are in the US-ASCII character ser (0x00..0x7F). + To know more read \ref wxjson_tutorial_unicode_ansi + + \par Efficiency + + In versions up to 1.0 the JSON writer wrote every character to the + output object (the string or the stream). + This is very inefficient becuase the writer converted each char to + UTF-8 when writing to streams but we have to note that only string values + have to be actually converted. + Special JSON characters, numbers and literals do not need the conversion + because they lay in the US-ASCII plane (0x00-0x7F) + and no conversion is needed as the UTF-8 encoding is the same as US-ASCII. + + For more info about the unicode topic see \ref wxjson_tutorial_unicode. + + \par The problem of writing doubles + + You can customize the ouput of doubles by specifing the format string + that has to be used by the JSON writer class. To know more about this issue + read \ref wxjson_tutorial_write_doubles +*/ + +//! Ctor. +/*! + Construct the JSON writer object with the specified parameters. + Note that if \c styled is FALSE the indentation is totally suppressed + and the values of the other two parameters are simply ignored. + + \param indent the initial indentation in number of spaces. Default is ZERO. + If you specify the wxJSONWRITER_TAB_INDENT flag for the \e style, + this value referes to the number of TABs in the initial indentation + + \param step the indentation increment for new objects/arrays in number of spaces + (default is 3). + This value is ignored if you specify the wxJSONWRITER_TAB_INDENT flag for + the \e style: the indentation increment is only one TAB character. + + \param style this is a combination of the following constants OR'ed togheter: + \li wxJSONWRITER_NONE: no indentation is performed and no LF character is + written between values. + This style produces strict JSON text but it is hard to read by humans + \li wxJSONWRITER_STYLED: output is human-readable: values are separated by + LF characters and sub-items are indented. + This style produces strict JSON text that is easy to read by humans. + \li wxJSONWRITER_WRITE_COMMENTS: this flag force the writer to write C/C++ + comment strings, if any. The comments will be written in their original position. + C/C++ comments may not be recognized by other JSON implementations because + they are not strict JSON text. + \li wxJSONWRITER_COMMENTS_BEFORE: this flag force the writer to write C/C++ comments + always before the value they refer to. + In order for this style to take effect, you also have to specify the + wxJSONWRITER_WRITE_COMMENTS flag. + \li wxJSONWRITER_COMMENTS_AFTER: this flag force the writer to write C/C++ comments + always after the value they refer to. + In order for this style to take effect, you also have to specify the + wxJSONWRITER_WRITE_COMMENTS flag. + \li wxJSONWRITER_SPLIT_STRINGS: this flag cause the writer to split strings + in more than one line if they are too long. + \li wxJSONWRITER_NO_LINEFEEDS: this flag cause the JSON writer to not add + newlines between values. It is ignored if wxJSONWRITER_STYLED is not set. + This style produces strict JSON text. + \li wxJSONWRITER_ESCAPE_SOLIDUS: the solidus character (/) should only be + escaped if the JSON text is meant for embedding in HTML. + Unlike in older 0.x versions, it is disabled by default and this flag cause + the solidus char to be escaped. + This style produces strict JSON text. + \li wxJSONWRITER_MULTILINE_STRING:this is a multiline-string mode where newlines + and tabs are not escaped. This is not strict JSON, but it helps immensely when + manually editing json files with multiline strings + \li wxJSONWRITER_RECOGNIZE_UNSIGNED: this flag cause the JSON writer to prepend + a plus sign (+) to unsigned integer values. This is used by the wxJSON reader to + force the integer to be stored in an \b unsigned \b int. Note that this + feature may be incompatible with other JSON implementations. + \li wxJSONWRITER_TAB_INDENT: this flag cause the indentation of sub-objects / arrays + to be done using a TAB character instead of SPACES. + In order for this style to take effect, you also have to specify the + wxJSONWRITER_STYLED flag. + This style produces strict JSON text. + \li wxJSONWRITER_NO_INDENTATION: this flag cause the JSON writer to not add + indentation. It is ignored if wxJSONWRITER_STYLED is not set. + This style produces strict JSON text. + \li wxJSONWRITER_NOUTF8_STREAM: suppress UTF-8 conversion when writing string + values to the stream thus producing ANSI text output; only meaningfull in + ANSI builds, this flag is simply ignored in Unicode builds. + \li wxJSONWRITER_MEMORYBUFF: + + + Note that for the style wxJSONWRITER_NONE the JSON text output is a bit + different from that of old 0.x versions although it is syntactically equal. + If you rely on the old JSON output formatting read the following page + \ref wxjson_tutorial_style_none. + To know more about the writer's styles see \ref wxjson_tutorial_style +*/ +wxJSONWriter::wxJSONWriter(int style, int indent, int step) +{ + m_indent = indent; + m_step = step; + m_style = style; + m_noUtf8 = false; + if (m_style == wxJSONWRITER_NONE) { + m_indent = 0; + m_step = 0; + } + // set the default format string for doubles as + // 10 significant digits and suppress trailing ZEROes + SetDoubleFmtString("%.10g"); + +#if !defined( wxJSON_USE_UNICODE ) + // in ANSI builds we can suppress UTF-8 conversion for both the writer and the reader + if (m_style == wxJSONWRITER_NOUTF8_STREAM) { + m_noUtf8 = true; + } +#endif +} + +//! Dtor - does nothing +wxJSONWriter::~wxJSONWriter() +{ +} + +//! Write the JSONvalue object to a JSON text. +/*! + The two overloaded versions of this function let the user choose + the output object which can be: + + \li a string object (\b wxString) + \li a stream object ( \b wxOutputStream) + + The two types of output object are very different because the + text outputted is encoded in different formats depending on the + build mode. + When writing to a string object, the JSON text output is encoded + differently depending on the build mode and the platform. + Writing to a stream always produce UTF-8 encoded text. + To know more about this topic read \ref wxjson_tutorial_unicode. + + Also note that the Write() function does not return a status code. + If you are writing to a string, you do not have to warry about this + issue: no errors can occur when writing to strings. + On the other hand, wehn writing to a stream there could be errors + in the write operation. + If an error occurs, the \c Write(9 function immediatly returns + without trying further output operations. + You have to check the status of the stream by calling the stream's + memberfunctions. Example: + + \code + // construct the JSON value object and add values to it + wxJSONValue root; + root["key1"] = "some value"; + + // write to a stream + wxMemoryOutputStream mem; + wxJSONWriter writer; + writer.Write( root, mem ); + wxStreamError err = mem.GetLastError(); + if ( err != wxSTREAM_NO_ERROR ) { + MessageBox( _T("ERROR: cannot write the JSON text output")); + } +\endcode +*/ +void +wxJSONWriter::Write(const wxJSONValue& value, wxString& str) +{ +#if !defined( wxJSON_USE_UNICODE ) + // in ANSI builds output to a string never use UTF-8 conversion + bool noUtf8_bak = m_noUtf8; // save the current setting + m_noUtf8 = true; +#endif + + wxMemoryOutputStream os; + Write(value, os); + + // get the address of the buffer + wxFileOffset len = os.GetLength(); + wxStreamBuffer* osBuff = os.GetOutputStreamBuffer(); + void* buffStart = osBuff->GetBufferStart(); + + if (m_noUtf8) { + str = wxString::From8BitData((const char*)buffStart, len); + } + else { + str = wxString::FromUTF8((const char*)buffStart, len); + } +#if !defined( wxJSON_USE_UNICODE ) + m_noUtf8 = noUtf8_bak; // restore the old setting +#endif +} + +//! \overload Write( const wxJSONValue&, wxString& ) +void +wxJSONWriter::Write(const wxJSONValue& value, wxOutputStream& os) +{ + m_level = 0; + DoWrite(os, value, 0, false); +} + +//! Set the format string for double values. +/*! + This function sets the format string used for printing double values. + Double values are outputted to JSON text using the \b snprintf function + with a default format string of: + \code + %.10g + \endcode + which prints doubles with a precision of 10 decimal digits and suppressing + trailing ZEROes. + + Note that the parameter is a pointer to \b char and not to \b wxChar. This + is because the JSON writer always procudes UTF-8 encoded text and decimal + digits in UTF-8 are made of only one UTF-8 code-unit (1 byte). +*/ +void +wxJSONWriter::SetDoubleFmtString(const char* fmt) +{ + m_fmt = (char*)fmt; +} + + + +//! Perform the real write operation. +/*! + This is a recursive function that gets the type of the \c value object and + calls several protected functions depending on the type: + + \li \c WriteNullvalue for type NULL + \li \c WriteStringValue() for STRING and CSTRING types + \li \c WriteIntValue for INT types + \li \c WriteUIntValue for UINT types + \li \c WriteBoolValue for BOOL types + \li \c WriteDoubleValue for DOUBLE types + \li \c WriteMemoryBuff for MEMORYBUFF types + + If the value is an array or key/value map (types ARRAY and OBJECT), the function + iterates through all JSON value object in the array/map and calls itself for every + item in the container. +*/ +int +wxJSONWriter::DoWrite(wxOutputStream& os, const wxJSONValue& value, const wxString* key, bool comma) +{ + // note that this function is recursive + + // some variables that cannot be allocated in the switch statement + const wxJSONInternalMap* map = 0; + int size; + m_colNo = 1; m_lineNo = 1; + // determine the comment position; it is one of: + // + // wxJSONVALUE_COMMENT_BEFORE + // wxJSONVALUE_COMMENT_AFTER + // wxJSONVALUE_COMMENT_INLINE + // + // or -1 if comments have not to be written + int commentPos = -1; + if (value.GetCommentCount() > 0 && (m_style & wxJSONWRITER_WRITE_COMMENTS)) { + commentPos = value.GetCommentPos(); + if ((m_style & wxJSONWRITER_COMMENTS_BEFORE) != 0) { + commentPos = wxJSONVALUE_COMMENT_BEFORE; + } + else if ((m_style & wxJSONWRITER_COMMENTS_AFTER) != 0) { + commentPos = wxJSONVALUE_COMMENT_AFTER; + } + } + + int lastChar = 0; // check if WriteComment() writes the last LF char + + // first write the comment if it is BEFORE + if (commentPos == wxJSONVALUE_COMMENT_BEFORE) { + lastChar = WriteComment(os, value, true); + if (lastChar < 0) { + return lastChar; + } + else if (lastChar != '\n') { + WriteSeparator(os); + } + } + + lastChar = WriteIndent(os); + if (lastChar < 0) { + return lastChar; + } + + // now write the key if it is not NULL + if (key) { + lastChar = WriteKey(os, *key); + } + if (lastChar < 0) { + return lastChar; + } + + // now write the value + wxJSONInternalMap::const_iterator it; // declare the map object + long int count = 0; + + wxJSONType t = value.GetType(); + switch (t) { + case wxJSONTYPE_INVALID: + WriteInvalid(os); + wxFAIL_MSG(_T("wxJSONWriter::WriteEmpty() cannot be called (not a valid JSON text")); + break; + + case wxJSONTYPE_INT: + case wxJSONTYPE_SHORT: + case wxJSONTYPE_LONG: + case wxJSONTYPE_INT64: + lastChar = WriteIntValue(os, value); + break; + + case wxJSONTYPE_UINT: + case wxJSONTYPE_USHORT: + case wxJSONTYPE_ULONG: + case wxJSONTYPE_UINT64: + lastChar = WriteUIntValue(os, value); + break; + + case wxJSONTYPE_NULL: + lastChar = WriteNullValue(os); + break; + case wxJSONTYPE_BOOL: + lastChar = WriteBoolValue(os, value); + break; + + case wxJSONTYPE_DOUBLE: + lastChar = WriteDoubleValue(os, value); + break; + + case wxJSONTYPE_STRING: + case wxJSONTYPE_CSTRING: + lastChar = WriteStringValue(os, value.AsString()); + break; + + case wxJSONTYPE_MEMORYBUFF: + lastChar = WriteMemoryBuff(os, value.AsMemoryBuff()); + break; + + case wxJSONTYPE_ARRAY: + ++m_level; + os.PutC('['); + // the inline comment for objects and arrays are printed in the open char + if (commentPos == wxJSONVALUE_COMMENT_INLINE) { + commentPos = -1; // we have already written the comment + lastChar = WriteComment(os, value, false); + if (lastChar < 0) { + return lastChar; + } + if (lastChar != '\n') { + lastChar = WriteSeparator(os); + } + } + else { // comment is not to be printed inline, so write a LF + lastChar = WriteSeparator(os); + if (lastChar < 0) { + return lastChar; + } + } + + // now iterate through all sub-items and call DoWrite() recursively + size = value.Size(); + for (int i = 0; i < size; i++) { + bool addComma = false; + if (i < size - 1) { + addComma = true; + } + wxJSONValue v = value.ItemAt(i); + lastChar = DoWrite(os, v, 0, addComma); + if (lastChar < 0) { + return lastChar; + } + + } + --m_level; + lastChar = WriteIndent(os); + if (lastChar < 0) { + return lastChar; + } + os.PutC(']'); + break; + + case wxJSONTYPE_OBJECT: + ++m_level; + + os.PutC('{'); + // the inline comment for objects and arrays are printed in the open char + if (commentPos == wxJSONVALUE_COMMENT_INLINE) { + commentPos = -1; // we have already written the comment + lastChar = WriteComment(os, value, false); + if (lastChar < 0) { + return lastChar; + } + if (lastChar != '\n') { + WriteSeparator(os); + } + } + else { + lastChar = WriteSeparator(os); + } + + map = value.AsMap(); + size = value.Size(); + count = 0; + for (it = map->begin(); it != map->end(); ++it) { + // get the key and the value + wxString nextKey = it->first; + const wxJSONValue& v = it->second; + bool addComma = false; + if (count < size - 1) { + addComma = true; + } + lastChar = DoWrite(os, v, &nextKey, addComma); + if (lastChar < 0) { + return lastChar; + } + count++; + } + --m_level; + lastChar = WriteIndent(os); + if (lastChar < 0) { + return lastChar; + } + os.PutC('}'); + break; + + default: + // a not yet defined wxJSONType: we FAIL + wxFAIL_MSG(_T("wxJSONWriter::DoWrite() undefined wxJSONType type")); + break; + } + + // writes the comma character before the inline comment + if (comma) { + os.PutC(','); + } + + if (commentPos == wxJSONVALUE_COMMENT_INLINE) { + lastChar = WriteComment(os, value, false); + if (lastChar < 0) { + return lastChar; + } + } + else if (commentPos == wxJSONVALUE_COMMENT_AFTER) { + WriteSeparator(os); + lastChar = WriteComment(os, value, true); + if (lastChar < 0) { + return lastChar; + } + } + if (lastChar != '\n') { + lastChar = WriteSeparator(os); + } + return lastChar; +} + + +//! Write the comment strings, if any. +int +wxJSONWriter::WriteComment(wxOutputStream& os, const wxJSONValue& value, bool indent) +{ + // the function returns the last character written which should be + // a LF char or -1 in case of errors + // if nothing is written, returns ZERO + int lastChar = 0; + + // only write comments if the style include the WRITE_COMMENTS flag + if ((m_style & wxJSONWRITER_WRITE_COMMENTS) == 0) { + return lastChar; + } + + const wxArrayString cmt = value.GetCommentArray(); + int cmtSize = cmt.GetCount(); + for (int i = 0; i < cmtSize; i++) { + if (indent) { + WriteIndent(os); + } + else { + os.PutC('\t'); + } + WriteString(os, cmt[i]); + lastChar = cmt[i].Last(); + if (lastChar != '\n') { + os.PutC('\n'); + lastChar = '\n'; + } + } + return lastChar; +} + +//! Writes the indentation to the JSON text. +/*! + The two functions write the indentation as \e spaces in the JSON output + text. When called with a int parameter, the function + writes the specified number of spaces. + If no parameter is given, the function computes the number of spaces + using the following formula: + If the wxJSONWRITER_TAB_INDENT flag is used in the writer's cnstructor, + the function calls WriteTabIndent(). + + The function also checks that wxJSONWRITER_STYLED is set and the + wxJSONWRITER_NO_INDENTATION is not set. +*/ +int +wxJSONWriter::WriteIndent(wxOutputStream& os) +{ + int lastChar = WriteIndent(os, m_level); + return lastChar; +} + +//! Write the specified number of indentation (spaces or tabs) +/*! + The function is called by WriteIndent() and other writer's functions. + It writes the indentation as specified in the \c num parameter which is + the actual \b level of annidation. + The function checks if wxJSONWRITER_STYLED is set: if not, no indentation + is performed. + Also, the function checks if wxJSONWRITER_TAB_INDENT is set: if it is, + indentation is done by writing \b num TAB characters otherwise, + it is performed by writing a number of spaces computed as: + \code + numSpaces = m_indent + ( m_step * num ) + \endcode + +*/ +int +wxJSONWriter::WriteIndent(wxOutputStream& os, int num) +{ + int lastChar = 0; + if (!(m_style & wxJSONWRITER_STYLED) || (m_style & wxJSONWRITER_NO_INDENTATION)) { + return lastChar; + } + + int numChars = m_indent + (m_step * num); + char c = ' '; + if (m_style & wxJSONWRITER_TAB_INDENT) { + c = '\t'; + numChars = num; + } + + for (int i = 0; i < numChars; i++) { + os.PutC(c); + if (os.GetLastError() != wxSTREAM_NO_ERROR) { + return -1; + } + + } + return c; +} + + +//! Write the provided string to the output object. +/*! + The function writes the string \c str to the output object that + was specified in the wxJSONWriter::Write() function. + The function may split strings in two or more lines if the + string contains LF characters if the \c m_style data member contains + the wxJSONWRITER_SPLIT_STRING flag. + + The function does not actually write the string: for every character + in the provided string the function calls WriteChar() which does + the actual character output. + + The function returns ZERO on success or -1 in case of errors. +*/ +int +wxJSONWriter::WriteStringValue(wxOutputStream& os, const wxString& str) +{ + // JSON values of type STRING are written by converting the whole string + // to UTF-8 and then copying the UTF-8 buffer to the 'os' stream + // one byte at a time and processing them + os.PutC('\"'); // open quotes + + // the buffer that has to be written is either UTF-8 or ANSI c_str() depending + // on the 'm_noUtf8' flag + char* writeBuff = 0; + wxCharBuffer utf8CB = str.ToUTF8(); // the UTF-8 buffer +#if !defined( wxJSON_USE_UNICODE ) + wxCharBuffer ansiCB(str.c_str()); // the ANSI buffer + if (m_noUtf8) { + writeBuff = ansiCB.data(); + } + else { + writeBuff = utf8CB.data(); + } +#else + writeBuff = utf8CB.data(); +#endif + + // NOTE: in ANSI builds UTF-8 conversion may fail (see samples/test5.cpp, + // test 7.3) although I do not know why + if (writeBuff == 0) { + const char* err = ""; + os.Write(err, strlen(err)); + return 0; + } + size_t len = strlen(writeBuff); + int lastChar = 0; + + // store the column at which the string starts + // splitting strings only happen if the string starts within + // column wxJSONWRITER_LAST_COL (default 50) + // see 'include/wx/json_defs.h' for the defines + int tempCol = m_colNo; + + // now write the UTF8 buffer processing the bytes + size_t i; + for (i = 0; i < len; i++) { + bool shouldEscape = false; + unsigned char ch = *writeBuff; + ++writeBuff; // point to the next byte + + // the escaped character + char escCh = 0; + + // for every character we have to check if it is a character that + // needs to be escaped: note that characters that should be escaped + // may be not if some writer's flags are specified + switch (ch) { + case '\"': // quotes + shouldEscape = true; + escCh = '\"'; + break; + case '\\': // reverse solidus + shouldEscape = true; + escCh = '\\'; + break; + case '/': // solidus + shouldEscape = true; + escCh = '/'; + break; + case '\b': // backspace + shouldEscape = true; + escCh = 'b'; + break; + case '\f': // formfeed + shouldEscape = true; + escCh = 'f'; + break; + case '\n': // newline + shouldEscape = true; + escCh = 'n'; + break; + case '\r': // carriage-return + shouldEscape = true; + escCh = 'r'; + break; + case '\t': // horizontal tab + shouldEscape = true; + escCh = 't'; + break; + default: + shouldEscape = false; + break; + } // end switch + + + // if the character is a control character that is not identified by a + // lowercase letter, we should escape it + if (!shouldEscape && ch < 32) { + char b[8]; + snprintf(b, 8, "\\u%04X", (int)ch); + os.Write(b, 6); + if (os.GetLastError() != wxSTREAM_NO_ERROR) { + return -1; + } + } + + // the char is not a control character + else { + // some characters that should be escaped are not escaped + // if the writer was constructed with some flags + if (shouldEscape && !(m_style & wxJSONWRITER_ESCAPE_SOLIDUS)) { + if (ch == '/') { + shouldEscape = false; + } + } + if (shouldEscape && (m_style & wxJSONWRITER_MULTILINE_STRING)) { + if (ch == '\n' || ch == '\t') { + shouldEscape = false; + } + } + + + // now write the character prepended by ESC if it should be escaped + if (shouldEscape) { + os.PutC('\\'); + os.PutC(escCh); + if (os.GetLastError() != wxSTREAM_NO_ERROR) { + return -1; + } + } + else { + // a normal char or a UTF-8 units: write the character + os.PutC(ch); + if (os.GetLastError() != wxSTREAM_NO_ERROR) { + return -1; + } + } + } + + // check if SPLIT_STRING flag is set and if the string has to + // be splitted + if ((m_style & wxJSONWRITER_STYLED) && (m_style & wxJSONWRITER_SPLIT_STRING)) { + // split the string if the character written is LF + if (ch == '\n') { + // close quotes and CR + os.Write("\"\n", 2); + lastChar = WriteIndent(os, m_level + 2); // write indentation + os.PutC('\"'); // reopen quotes + if (lastChar < 0) { + return lastChar; + } + } + // split the string only if there is at least wxJSONWRITER_MIN_LENGTH + // character to write and the character written is a punctuation or space + // BUG: the following does not work because the columns are not counted + else if ((m_colNo >= wxJSONWRITER_SPLIT_COL) + && (tempCol <= wxJSONWRITER_LAST_COL)) { + if (IsSpace(ch) || IsPunctuation(ch)) { + if (len - i > wxJSONWRITER_MIN_LENGTH) { + // close quotes and CR + os.Write("\"\n", 2); + lastChar = WriteIndent(os, m_level + 2); // write indentation + os.PutC('\"'); // reopen quotes + if (lastChar < 0) { + return lastChar; + } + } + } + } + } + } // end for + os.PutC('\"'); // close quotes + return 0; +} + + + +//! Write a generic string +/*! + The function writes the wxString object \c str to the output object. + The string is written as is; you cannot use it to write JSON strings + to the output text. + The function converts the string \c str to UTF-8 and writes the buffer.. +*/ +int +wxJSONWriter::WriteString(wxOutputStream& os, const wxString& str) +{ + wxLogTrace(writerTraceMask, _T("(%s) string to write=%s"), + __PRETTY_FUNCTION__, str.c_str()); + int lastChar = 0; + char* writeBuff = 0; + + // the buffer that has to be written is either UTF-8 or ANSI c_str() depending + // on the 'm_noUtf8' flag + wxCharBuffer utf8CB = str.ToUTF8(); // the UTF-8 buffer +#if !defined( wxJSON_USE_UNICODE ) + wxCharBuffer ansiCB(str.c_str()); // the ANSI buffer + + if (m_noUtf8) { + writeBuff = ansiCB.data(); + } + else { + writeBuff = utf8CB.data(); + } +#else + writeBuff = utf8CB.data(); +#endif + + // NOTE: in ANSI builds UTF-8 conversion may fail (see samples/test5.cpp, + // test 7.3) although I do not know why + if (writeBuff == 0) { + const char* err = ""; + os.Write(err, strlen(err)); + return 0; + } + size_t len = strlen(writeBuff); + + os.Write(writeBuff, len); + if (os.GetLastError() != wxSTREAM_NO_ERROR) { + return -1; + } + + wxLogTrace(writerTraceMask, _T("(%s) result=%d"), + __PRETTY_FUNCTION__, lastChar); + return lastChar; +} + +//! Write the NULL JSON value to the output stream. +/*! + The function writes the \b null literal string to the output stream. +*/ +int +wxJSONWriter::WriteNullValue(wxOutputStream& os) +{ + os.Write("null", 4); + if (os.GetLastError() != wxSTREAM_NO_ERROR) { + return -1; + } + return 0; +} + + +//! Writes a value of type INT. +/*! + This function is called for every value objects of INT type. + This function uses the \n snprintf function to get the US-ASCII + representation of the integer and simply copy it to the output stream. + Returns -1 on stream errors or ZERO if no errors. +*/ +int +wxJSONWriter::WriteIntValue(wxOutputStream& os, const wxJSONValue& value) +{ + int r = 0; + char buffer[32]; // need to store 64-bits integers (max 20 digits) + size_t len; + + wxJSONRefData* data = value.GetRefData(); + wxASSERT(data); + +#if defined( wxJSON_64BIT_INT ) +#if wxCHECK_VERSION(2, 9, 0 ) || !defined( wxJSON_USE_UNICODE ) + // this is fine for wxW 2.9 and for wxW 2.8 ANSI + snprintf(buffer, 32, "%" wxLongLongFmtSpec "d", + data->m_value.m_valInt64); +#else + // this is for wxW 2.8 Unicode: in order to use the cross-platform + // format specifier, we use the wxString's sprintf() function and then + // convert to UTF-8 before writing to the stream + wxString s; + s.Printf(_T("%") wxLongLongFmtSpec _T("d"), + data->m_value.m_valInt64); + wxCharBuffer cb = s.ToUTF8(); + const char* cbData = cb.data(); + len = strlen(cbData); + wxASSERT(len < 32); + memcpy(buffer, cbData, len); + buffer[len] = 0; +#endif +#else + snprintf(buffer, 32, "%ld", data->m_value.m_valLong); +#endif + len = strlen(buffer); + os.Write(buffer, len); + if (os.GetLastError() != wxSTREAM_NO_ERROR) { + r = -1; + } + return r; +} + +//! Writes a value of type UNSIGNED INT. +/*! + This function is called for every value objects of UINT type. + This function uses the \n snprintf function to get the US-ASCII + representation of the integer and simply copy it to the output stream. + The function prepends a \b plus \b sign if the \c wxJSONWRITER_RECOGNIZE_UNSIGNED + flag is set in the \c m_flags data member. + Returns -1 on stream errors or ZERO if no errors. +*/ +int +wxJSONWriter::WriteUIntValue(wxOutputStream& os, const wxJSONValue& value) +{ + int r = 0; size_t len; + + // prepend a plus sign if the style specifies that unsigned integers + // have to be recognized by the JSON reader + if (m_style & wxJSONWRITER_RECOGNIZE_UNSIGNED) { + os.PutC('+'); + } + + char buffer[32]; // need to store 64-bits integers (max 20 digits) + wxJSONRefData* data = value.GetRefData(); + wxASSERT(data); + +#if defined( wxJSON_64BIT_INT ) +#if wxCHECK_VERSION(2, 9, 0 ) || !defined( wxJSON_USE_UNICODE ) + // this is fine for wxW 2.9 and for wxW 2.8 ANSI + snprintf(buffer, 32, "%" wxLongLongFmtSpec "u", + data->m_value.m_valUInt64); +#else + // this is for wxW 2.8 Unicode: in order to use the cross-platform + // format specifier, we use the wxString's sprintf() function and then + // convert to UTF-8 before writing to the stream + wxString s; + s.Printf(_T("%") wxLongLongFmtSpec _T("u"), + data->m_value.m_valInt64); + wxCharBuffer cb = s.ToUTF8(); + const char* cbData = cb.data(); + len = strlen(cbData); + wxASSERT(len < 32); + memcpy(buffer, cbData, len); + buffer[len] = 0; +#endif +#else + snprintf(buffer, 32, "%lu", data->m_value.m_valULong); +#endif + len = strlen(buffer); + os.Write(buffer, len); + if (os.GetLastError() != wxSTREAM_NO_ERROR) { + r = -1; + } + return r; +} + +//! Writes a value of type DOUBLE. +/*! + This function is called for every value objects of DOUBLE type. + This function uses the \n snprintf function to get the US-ASCII + representation of the integer and simply copy it to the output stream. + Returns -1 on stream errors or ZERO if no errors. + + Note that writing a double to a decimal ASCII representation could + lay to unexpected results depending on the format string used in the + conversion. + See SetDoubleFmtString for details. +*/ +int +wxJSONWriter::WriteDoubleValue(wxOutputStream& os, const wxJSONValue& value) +{ + int r = 0; + + char buffer[32]; + wxJSONRefData* data = value.GetRefData(); + wxASSERT(data); + snprintf(buffer, 32, m_fmt, data->m_value.m_valDouble); + size_t len = strlen(buffer); + os.Write(buffer, len); + if (os.GetLastError() != wxSTREAM_NO_ERROR) { + r = -1; + } + return r; +} + +//! Writes a value of type BOOL. +/*! + This function is called for every value objects of BOOL type. + This function prints the literals \b true or \b false depending on the + value in \c value. + Returns -1 on stream errors or ZERO if no errors. +*/ +int +wxJSONWriter::WriteBoolValue(wxOutputStream& os, const wxJSONValue& value) +{ + int r = 0; + const char* f = "false"; const char* t = "true"; + wxJSONRefData* data = value.GetRefData(); + wxASSERT(data); + + const char* c = f; // defaults to FALSE + + if (data->m_value.m_valBool) { + c = t; + } + + size_t len = strlen(c); + os.Write(c, len); + if (os.GetLastError() != wxSTREAM_NO_ERROR) { + r = -1; + } + return r; +} + + + + +//! Write the key of a key/value element to the output stream. +int +wxJSONWriter::WriteKey(wxOutputStream& os, const wxString& key) +{ + wxLogTrace(writerTraceMask, _T("(%s) key write=%s"), + __PRETTY_FUNCTION__, key.c_str()); + + int lastChar = WriteStringValue(os, key); + os.Write(" : ", 3); + return lastChar; +} + +//! Write the invalid JSON value to the output stream. +/*! + An invalid wxJSONValue is a value that was not initialized and it is + an error. You should never write invalid values to JSON text because + the output is not valid JSON text. + Note that the NULL value is a legal JSON text and it is written: + \code + null + \endcode + + This function writes a non-JSON text to the output stream: + \code + + \endcode + In debug mode, the function always fails with an wxFAIL_MSG failure. +*/ +int +wxJSONWriter::WriteInvalid(wxOutputStream& os) +{ + wxFAIL_MSG(_T("wxJSONWriter::WriteInvalid() cannot be called (not a valid JSON text")); + int lastChar = 0; + os.Write("", 9); + return lastChar; +} + +//! Write a JSON value of type \e memory \e buffer +/*! + The type wxJSONTYPE_MEMORYBUFF is a \b wxJSON extension that is not correctly read by + other JSON implementations. + By default, the function writes such a type as an array of INTs as follows: + \code + [ 0,32,45,255,6,...] + \endcode + If the writer object was constructed using the \c wxJSONWRITER_MEMORYBUFF flag, then + the output is much more compact and recognized by the \b wxJSON reader as a memory buffer + type: + \code + '00203FFF06..' + \endcode + +*/ +int +wxJSONWriter::WriteMemoryBuff(wxOutputStream& os, const wxMemoryBuffer& buff) +{ +#define MAX_BYTES_PER_ROW 20 + char str[16]; + + // if STYLED and SPLIT_STRING flags are set, the function writes 20 bytes on every row + // the following is the counter of bytes written. + // the string is splitted only for the special meory buffer type, not for array of INTs + int bytesWritten = 0; + bool splitString = false; + if ((m_style & wxJSONWRITER_STYLED) && + (m_style & wxJSONWRITER_SPLIT_STRING)) { + splitString = true; + } + + size_t buffLen = buff.GetDataLen(); + unsigned char* ptr = (unsigned char*)buff.GetData(); + wxASSERT(ptr); + char openChar = '\''; + char closeChar = '\''; + bool asArray = false; + + if ((m_style & wxJSONWRITER_MEMORYBUFF) == 0) { + // if the special flag is not specified, write as an array of INTs + openChar = '['; + closeChar = ']'; + asArray = true; + } + // write the open character + os.PutC(openChar); + + for (size_t i = 0; i < buffLen; i++) { + unsigned char c = *ptr; + ++ptr; + + if (asArray) { + snprintf(str, 14, "%d", c); + size_t len = strlen(str); + wxASSERT(len <= 3); + wxASSERT(len >= 1); + str[len] = ','; + // do not write the comma char for the last element + if (i < buffLen - 1) { + ++len; + } + os.Write(str, len); + if (os.GetLastError() != wxSTREAM_NO_ERROR) { + return -1; + } + } + else { + // now convert the byte in two hex digits + char c1 = c / 16; + char c2 = c % 16; + c1 += '0'; + c2 += '0'; + if (c1 > '9') { + c1 += 7; + } + if (c2 > '9') { + c2 += 7; + } + os.PutC(c1); + os.PutC(c2); + if (os.GetLastError() != wxSTREAM_NO_ERROR) { + return -1; + } + if (splitString) { + ++bytesWritten; + } + + if ((bytesWritten >= MAX_BYTES_PER_ROW) && ((buffLen - i) >= 5)) { + // split the string if we wrote 20 bytes, but only is we have to + // write at least 5 bytes + os.Write("\'\n", 2); + int lastChar = WriteIndent(os, m_level + 2); // write indentation + os.PutC('\''); // reopen quotes + if (lastChar < 0) { + return lastChar; + } + bytesWritten = 0; + } + } + } + + // write the close character + os.PutC(closeChar); + return closeChar; +} + + +//! Writes the separator between values +/*! + The function is called when a value has been written to the JSON + text output and it writes the separator character: LF. + The LF char is actually written only if the wxJSONWRITER_STYLED flag + is specified and wxJSONWRITER_NO_LINEFEEDS is not set. + + Returns the last character written which is LF itself or -1 in case + of errors. Note that LF is returned even if the character is not + actually written. +*/ +int +wxJSONWriter::WriteSeparator(wxOutputStream& os) +{ + int lastChar = '\n'; + if ((m_style & wxJSONWRITER_STYLED) && !(m_style & wxJSONWRITER_NO_LINEFEEDS)) { + os.PutC('\n'); + } + return lastChar; +} + +//! Returns TRUE if the character is a space character. +bool +wxJSONWriter::IsSpace(wxChar ch) +{ + bool r = false; + switch (ch) { + case ' ': + case '\t': + case '\r': + case '\f': + case '\n': + r = true; + break; + default: + break; + } + return r; +} + +//! Returns TRUE if the character if a puctuation character +bool +wxJSONWriter::IsPunctuation(wxChar ch) +{ + bool r = false; + switch (ch) { + case '.': + case ',': + case ';': + case ':': + case '!': + case '?': + r = true; + break; + default: + break; + } + return r; +} + + +/* +{ +} +*/ + + + +