diff --git a/MtApi/MtApi.csproj b/MtApi/MtApi.csproj index 6cad83ce..a68755fc 100755 --- a/MtApi/MtApi.csproj +++ b/MtApi/MtApi.csproj @@ -43,9 +43,14 @@ + + ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True + + @@ -56,8 +61,11 @@ + + + @@ -67,6 +75,14 @@ + + + + + + + + @@ -78,6 +94,10 @@ MTApiService + + + + diff --git a/MtApi/MtApiClient.cs b/MtApi/MtApiClient.cs index 8e6eb504..27ee271f 100755 --- a/MtApi/MtApiClient.cs +++ b/MtApi/MtApiClient.cs @@ -6,6 +6,10 @@ using MTApiService; using System.Drawing; using System.Collections; using System.Net; +using System.ServiceModel; +using MtApi.Requests; +using MtApi.Responses; +using Newtonsoft.Json; namespace MtApi { @@ -345,6 +349,18 @@ namespace MtApi { return sendCommand(MtCommandType.OrderCloseAll, null); } + + public MtOrder GetOrder(int index, OrderSelectMode select, OrderSelectSource pool) + { + var response = SendRequest(new GetOrderRequest { Index = index, Select = (int) select, Pool = (int) pool}); + return response != null ? response.Order : null; + } + + public IEnumerable GetOrders(OrderSelectSource pool) + { + var response = SendRequest(new GetOrdersRequest { Pool = (int)pool }); + return response != null ? response.Orders : null; + } #endregion #region Check Status @@ -1310,6 +1326,38 @@ namespace MtApi return result; } + private T SendRequest(RequestBase request) where T : ResponseBase, new() + { + if (request == null) + return default(T); + + var serializer = RequestContainer.CreateNew(request).Serialize(); + var commandParameters = new ArrayList { serializer }; + + MtResponseString res; + try + { + res = (MtResponseString)mClient.SendCommand((int)MtCommandType.MtRequest, commandParameters); + } + catch (CommunicationException ex) + { + throw new MtConnectionException(ex.Message, ex); + } + + if (res == null) + { + throw new MtExecutionException(-1, "Response from MetaTrader is null"); + } + + var response = JsonConvert.DeserializeObject(res.Value); + if (response.ErrorCode != 0) + { + throw new MtExecutionException(response.ErrorCode, response.ErrorMessage); + } + + return response; + } + private void mClient_QuoteUpdated(MTApiService.MtQuote quote) { if (quote != null) diff --git a/MtApi/MtCommandType.cs b/MtApi/MtCommandType.cs index 5c24f404..ac23fb42 100755 --- a/MtApi/MtCommandType.cs +++ b/MtApi/MtCommandType.cs @@ -188,6 +188,9 @@ namespace MtApi RefreshRates = 150, // TerminalInfoString = 153, - SymbolInfoString = 154 + SymbolInfoString = 154, + + //Requests + MtRequest = 155 } } diff --git a/MtApi/MtConnectionException.cs b/MtApi/MtConnectionException.cs new file mode 100644 index 00000000..3f705707 --- /dev/null +++ b/MtApi/MtConnectionException.cs @@ -0,0 +1,23 @@ +using System; + +namespace MtApi +{ + public class MtConnectionException: Exception + { + public MtConnectionException() + : this(null, null) + { + } + + public MtConnectionException(string message) + : this(message, null) + { + } + + public MtConnectionException(string message, Exception exception) + : base(message, exception) + { + } + + } +} \ No newline at end of file diff --git a/MtApi/MtExecutionException.cs b/MtApi/MtExecutionException.cs new file mode 100644 index 00000000..ecec0107 --- /dev/null +++ b/MtApi/MtExecutionException.cs @@ -0,0 +1,15 @@ +using System; + +namespace MtApi +{ + public class MtExecutionException: Exception + { + public MtExecutionException(int errorCode, string message) + :base(message) + { + ErrorCode = errorCode; + } + + public int ErrorCode { get; private set; } + } +} \ No newline at end of file diff --git a/MtApi/MtOrder.cs b/MtApi/MtOrder.cs new file mode 100644 index 00000000..da9da72b --- /dev/null +++ b/MtApi/MtOrder.cs @@ -0,0 +1,30 @@ +using System; + +namespace MtApi +{ + public class MtOrder + { + public int Ticket { get; set; } + public string Symbol { get; set; } + public TradeOperation Operation { get; set; } + public double OpenPrice { get; set; } + public double ClosePrice { get; set; } + public double Lots { get; set; } + public int MtOpenTime { get; set; } + public int MtCloseTime { get; set; } + public double Profit { get; set; } + public string Comment { get; set; } + public double Commission { get; set; } + public int MagicNumber { get; set; } + + public DateTime OpenTime + { + get { return MtApiTimeConverter.ConvertFromMtTime(MtOpenTime); } + } + + public DateTime CloseTime + { + get { return MtApiTimeConverter.ConvertFromMtTime(MtCloseTime); } + } + } +} \ No newline at end of file diff --git a/MtApi/Requests/GetOrderRequest.cs b/MtApi/Requests/GetOrderRequest.cs new file mode 100644 index 00000000..cb9138f2 --- /dev/null +++ b/MtApi/Requests/GetOrderRequest.cs @@ -0,0 +1,9 @@ +namespace MtApi.Requests +{ + public class GetOrderRequest: RequestBase + { + public int Index { get; set; } + public int Select { get; set; } + public int Pool { get; set; } + } +} \ No newline at end of file diff --git a/MtApi/Requests/GetOrdersRequest.cs b/MtApi/Requests/GetOrdersRequest.cs new file mode 100644 index 00000000..cd3f1ffb --- /dev/null +++ b/MtApi/Requests/GetOrdersRequest.cs @@ -0,0 +1,7 @@ +namespace MtApi.Requests +{ + public class GetOrdersRequest: RequestBase + { + public int Pool { get; set; } + } +} \ No newline at end of file diff --git a/MtApi/Requests/RequestBase.cs b/MtApi/Requests/RequestBase.cs new file mode 100644 index 00000000..fac684c9 --- /dev/null +++ b/MtApi/Requests/RequestBase.cs @@ -0,0 +1,6 @@ +namespace MtApi.Requests +{ + public abstract class RequestBase + { + } +} diff --git a/MtApi/Requests/RequestContainer.cs b/MtApi/Requests/RequestContainer.cs new file mode 100644 index 00000000..9075086f --- /dev/null +++ b/MtApi/Requests/RequestContainer.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace MtApi.Requests +{ + public class RequestContainer + { + private static readonly Dictionary RequestTypes = new Dictionary(); + + static RequestContainer() + { + RequestTypes[typeof (GetOrderRequest)] = RequestType.GetOrder; + RequestTypes[typeof(GetOrdersRequest)] = RequestType.GetOrders; + } + + public RequestType RequestType { get; set; } + public RequestBase Request { get; set; } + + public virtual string Serialize() + { + return JsonConvert.SerializeObject(this); + } + + public static RequestContainer CreateNew(T request) where T: RequestBase + { + if (RequestTypes.ContainsKey(request.GetType()) == false) + throw new ArgumentException("Unknown request type"); + + return new RequestContainer { RequestType = RequestTypes[request.GetType()], Request = request }; + } + } +} \ No newline at end of file diff --git a/MtApi/Requests/RequestType.cs b/MtApi/Requests/RequestType.cs new file mode 100644 index 00000000..dca2cd9f --- /dev/null +++ b/MtApi/Requests/RequestType.cs @@ -0,0 +1,9 @@ +namespace MtApi.Requests +{ + public enum RequestType + { + Unknown = 0, + GetOrder = 1, + GetOrders = 2 + } +} \ No newline at end of file diff --git a/MtApi/Responses/GetOrderResponse.cs b/MtApi/Responses/GetOrderResponse.cs new file mode 100644 index 00000000..a99d3b68 --- /dev/null +++ b/MtApi/Responses/GetOrderResponse.cs @@ -0,0 +1,7 @@ +namespace MtApi.Responses +{ + public class GetOrderResponse: ResponseBase + { + public MtOrder Order { get; set; } + } +} \ No newline at end of file diff --git a/MtApi/Responses/GetOrdersResponse.cs b/MtApi/Responses/GetOrdersResponse.cs new file mode 100644 index 00000000..20e927a1 --- /dev/null +++ b/MtApi/Responses/GetOrdersResponse.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace MtApi.Responses +{ + public class GetOrdersResponse: ResponseBase + { + public List Orders { get; set; } + } +} \ No newline at end of file diff --git a/MtApi/Responses/ResponseBase.cs b/MtApi/Responses/ResponseBase.cs new file mode 100644 index 00000000..470f0b6a --- /dev/null +++ b/MtApi/Responses/ResponseBase.cs @@ -0,0 +1,8 @@ +namespace MtApi.Responses +{ + public class ResponseBase + { + public int ErrorCode { get; set; } + public string ErrorMessage { get; set; } + } +} \ No newline at end of file diff --git a/MtApi/packages.config b/MtApi/packages.config new file mode 100644 index 00000000..55658b8f --- /dev/null +++ b/MtApi/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/TestApiClientUI/Form1.Designer.cs b/TestApiClientUI/Form1.Designer.cs index 0fab002c..2eaa6e37 100755 --- a/TestApiClientUI/Form1.Designer.cs +++ b/TestApiClientUI/Form1.Designer.cs @@ -28,6 +28,7 @@ /// private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); this.textBoxServerName = new System.Windows.Forms.TextBox(); this.textBoxPort = new System.Windows.Forms.TextBox(); this.buttonConnect = new System.Windows.Forms.Button(); @@ -46,6 +47,10 @@ this.listBoxEventLog = new System.Windows.Forms.ListBox(); this.tabControl1 = new System.Windows.Forms.TabControl(); this.tabPage2 = new System.Windows.Forms.TabPage(); + this.button17 = new System.Windows.Forms.Button(); + this.comboBox2 = new System.Windows.Forms.ComboBox(); + this.comboBox1 = new System.Windows.Forms.ComboBox(); + this.button16 = new System.Windows.Forms.Button(); this.label21 = new System.Windows.Forms.Label(); this.textBoxIndexTicket = new System.Windows.Forms.TextBox(); this.comboBoxSelectedCommand = new System.Windows.Forms.ComboBox(); @@ -79,6 +84,7 @@ this.label9 = new System.Windows.Forms.Label(); this.listBoxSendedOrders = new System.Windows.Forms.ListBox(); this.tabPage1 = new System.Windows.Forms.TabPage(); + this.buttonRefreshRates = new System.Windows.Forms.Button(); this.label22 = new System.Windows.Forms.Label(); this.textBoxErrorCode = new System.Windows.Forms.TextBox(); this.button3 = new System.Windows.Forms.Button(); @@ -111,9 +117,10 @@ this.button6 = new System.Windows.Forms.Button(); this.listBoxProceHistory = new System.Windows.Forms.ListBox(); this.tabPage6 = new System.Windows.Forms.TabPage(); + this.button15 = new System.Windows.Forms.Button(); this.button14 = new System.Windows.Forms.Button(); this.button13 = new System.Windows.Forms.Button(); - this.buttonRefreshRates = new System.Windows.Forms.Button(); + this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components); this.groupBox1.SuspendLayout(); this.statusStrip1.SuspendLayout(); this.groupBox2.SuspendLayout(); @@ -202,7 +209,7 @@ this.toolStripStatusConnection}); this.statusStrip1.Location = new System.Drawing.Point(0, 637); this.statusStrip1.Name = "statusStrip1"; - this.statusStrip1.Size = new System.Drawing.Size(638, 22); + this.statusStrip1.Size = new System.Drawing.Size(713, 22); this.statusStrip1.TabIndex = 11; this.statusStrip1.Text = "statusStrip1"; // @@ -225,6 +232,9 @@ // // listViewQuotes // + this.listViewQuotes.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); this.listViewQuotes.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { this.colSymbol, this.colBid, @@ -256,14 +266,20 @@ // // listBoxEventLog // + this.listBoxEventLog.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.listBoxEventLog.ContextMenuStrip = this.contextMenuStrip1; this.listBoxEventLog.FormattingEnabled = true; this.listBoxEventLog.Location = new System.Drawing.Point(12, 543); this.listBoxEventLog.Name = "listBoxEventLog"; - this.listBoxEventLog.Size = new System.Drawing.Size(611, 82); + this.listBoxEventLog.Size = new System.Drawing.Size(690, 82); this.listBoxEventLog.TabIndex = 14; // // tabControl1 // + this.tabControl1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); this.tabControl1.Controls.Add(this.tabPage2); this.tabControl1.Controls.Add(this.tabPage1); this.tabControl1.Controls.Add(this.tabPage3); @@ -273,11 +289,15 @@ this.tabControl1.Location = new System.Drawing.Point(12, 147); this.tabControl1.Name = "tabControl1"; this.tabControl1.SelectedIndex = 0; - this.tabControl1.Size = new System.Drawing.Size(615, 388); + this.tabControl1.Size = new System.Drawing.Size(690, 388); this.tabControl1.TabIndex = 15; // // tabPage2 // + this.tabPage2.Controls.Add(this.button17); + this.tabPage2.Controls.Add(this.comboBox2); + this.tabPage2.Controls.Add(this.comboBox1); + this.tabPage2.Controls.Add(this.button16); this.tabPage2.Controls.Add(this.label21); this.tabPage2.Controls.Add(this.textBoxIndexTicket); this.tabPage2.Controls.Add(this.comboBoxSelectedCommand); @@ -290,26 +310,71 @@ this.tabPage2.Location = new System.Drawing.Point(4, 22); this.tabPage2.Name = "tabPage2"; this.tabPage2.Padding = new System.Windows.Forms.Padding(3); - this.tabPage2.Size = new System.Drawing.Size(607, 362); + this.tabPage2.Size = new System.Drawing.Size(682, 362); this.tabPage2.TabIndex = 1; this.tabPage2.Text = "Trade Operations"; this.tabPage2.UseVisualStyleBackColor = true; // + // button17 + // + this.button17.Location = new System.Drawing.Point(601, 290); + this.button17.Name = "button17"; + this.button17.Size = new System.Drawing.Size(75, 23); + this.button17.TabIndex = 12; + this.button17.Text = "GetOrders"; + this.button17.UseVisualStyleBackColor = true; + this.button17.Click += new System.EventHandler(this.button17_Click); + // + // comboBox2 + // + this.comboBox2.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBox2.FormattingEnabled = true; + this.comboBox2.Items.AddRange(new object[] { + "MODE_TRADES", + "MODE_HISTORY"}); + this.comboBox2.Location = new System.Drawing.Point(481, 261); + this.comboBox2.Name = "comboBox2"; + this.comboBox2.Size = new System.Drawing.Size(114, 21); + this.comboBox2.TabIndex = 11; + // + // comboBox1 + // + this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBox1.FormattingEnabled = true; + this.comboBox1.Items.AddRange(new object[] { + "SELECT_BY_POS", + "SELECT_BY_TICKET"}); + this.comboBox1.Location = new System.Drawing.Point(356, 261); + this.comboBox1.Name = "comboBox1"; + this.comboBox1.Size = new System.Drawing.Size(119, 21); + this.comboBox1.TabIndex = 10; + // + // button16 + // + this.button16.Location = new System.Drawing.Point(601, 261); + this.button16.Name = "button16"; + this.button16.Size = new System.Drawing.Size(75, 23); + this.button16.TabIndex = 9; + this.button16.Text = "GetOrder"; + this.button16.UseVisualStyleBackColor = true; + this.button16.Click += new System.EventHandler(this.button16_Click); + // // label21 // this.label21.AutoSize = true; this.label21.Location = new System.Drawing.Point(213, 264); this.label21.Name = "label21"; - this.label21.Size = new System.Drawing.Size(36, 13); + this.label21.Size = new System.Drawing.Size(71, 13); this.label21.TabIndex = 8; - this.label21.Text = "Index:"; + this.label21.Text = "Index/Ticket:"; // // textBoxIndexTicket // - this.textBoxIndexTicket.Location = new System.Drawing.Point(255, 261); + this.textBoxIndexTicket.Location = new System.Drawing.Point(290, 261); this.textBoxIndexTicket.Name = "textBoxIndexTicket"; this.textBoxIndexTicket.Size = new System.Drawing.Size(60, 20); this.textBoxIndexTicket.TabIndex = 7; + this.textBoxIndexTicket.Text = "0"; // // comboBoxSelectedCommand // @@ -364,10 +429,12 @@ // // listBoxClosedOrders // + this.listBoxClosedOrders.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); this.listBoxClosedOrders.FormattingEnabled = true; this.listBoxClosedOrders.Location = new System.Drawing.Point(213, 151); this.listBoxClosedOrders.Name = "listBoxClosedOrders"; - this.listBoxClosedOrders.Size = new System.Drawing.Size(388, 108); + this.listBoxClosedOrders.Size = new System.Drawing.Size(463, 108); this.listBoxClosedOrders.TabIndex = 5; // // button2 @@ -613,11 +680,14 @@ // // listBoxSendedOrders // + this.listBoxSendedOrders.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); this.listBoxSendedOrders.FormattingEnabled = true; this.listBoxSendedOrders.Location = new System.Drawing.Point(213, 23); this.listBoxSendedOrders.Name = "listBoxSendedOrders"; this.listBoxSendedOrders.SelectionMode = System.Windows.Forms.SelectionMode.MultiSimple; - this.listBoxSendedOrders.Size = new System.Drawing.Size(388, 108); + this.listBoxSendedOrders.Size = new System.Drawing.Size(463, 108); this.listBoxSendedOrders.TabIndex = 1; // // tabPage1 @@ -630,11 +700,21 @@ this.tabPage1.Location = new System.Drawing.Point(4, 22); this.tabPage1.Name = "tabPage1"; this.tabPage1.Padding = new System.Windows.Forms.Padding(3); - this.tabPage1.Size = new System.Drawing.Size(607, 362); + this.tabPage1.Size = new System.Drawing.Size(682, 362); this.tabPage1.TabIndex = 2; this.tabPage1.Text = "Check Status"; this.tabPage1.UseVisualStyleBackColor = true; // + // buttonRefreshRates + // + this.buttonRefreshRates.Location = new System.Drawing.Point(10, 172); + this.buttonRefreshRates.Name = "buttonRefreshRates"; + this.buttonRefreshRates.Size = new System.Drawing.Size(128, 23); + this.buttonRefreshRates.TabIndex = 4; + this.buttonRefreshRates.Text = "RefreshRates"; + this.buttonRefreshRates.UseVisualStyleBackColor = true; + this.buttonRefreshRates.Click += new System.EventHandler(this.buttonRefreshRates_Click); + // // label22 // this.label22.AutoSize = true; @@ -698,7 +778,7 @@ this.tabPage3.Location = new System.Drawing.Point(4, 22); this.tabPage3.Name = "tabPage3"; this.tabPage3.Padding = new System.Windows.Forms.Padding(3); - this.tabPage3.Size = new System.Drawing.Size(607, 362); + this.tabPage3.Size = new System.Drawing.Size(682, 362); this.tabPage3.TabIndex = 3; this.tabPage3.Text = "Account Information"; this.tabPage3.UseVisualStyleBackColor = true; @@ -809,7 +889,7 @@ this.tabPage4.Location = new System.Drawing.Point(4, 22); this.tabPage4.Name = "tabPage4"; this.tabPage4.Padding = new System.Windows.Forms.Padding(3); - this.tabPage4.Size = new System.Drawing.Size(607, 362); + this.tabPage4.Size = new System.Drawing.Size(682, 362); this.tabPage4.TabIndex = 4; this.tabPage4.Text = "MarketInfo"; this.tabPage4.UseVisualStyleBackColor = true; @@ -891,7 +971,7 @@ this.tabPage5.Location = new System.Drawing.Point(4, 22); this.tabPage5.Name = "tabPage5"; this.tabPage5.Padding = new System.Windows.Forms.Padding(3); - this.tabPage5.Size = new System.Drawing.Size(607, 362); + this.tabPage5.Size = new System.Drawing.Size(682, 362); this.tabPage5.TabIndex = 5; this.tabPage5.Text = "Timeframes"; this.tabPage5.UseVisualStyleBackColor = true; @@ -1009,16 +1089,27 @@ // // tabPage6 // + this.tabPage6.Controls.Add(this.button15); this.tabPage6.Controls.Add(this.button14); this.tabPage6.Controls.Add(this.button13); this.tabPage6.Location = new System.Drawing.Point(4, 22); this.tabPage6.Name = "tabPage6"; this.tabPage6.Padding = new System.Windows.Forms.Padding(3); - this.tabPage6.Size = new System.Drawing.Size(607, 362); + this.tabPage6.Size = new System.Drawing.Size(682, 362); this.tabPage6.TabIndex = 6; this.tabPage6.Text = "Client Terminal"; this.tabPage6.UseVisualStyleBackColor = true; // + // button15 + // + this.button15.Location = new System.Drawing.Point(223, 184); + this.button15.Name = "button15"; + this.button15.Size = new System.Drawing.Size(75, 23); + this.button15.TabIndex = 2; + this.button15.Text = "button15"; + this.button15.UseVisualStyleBackColor = true; + this.button15.Click += new System.EventHandler(this.button15_Click); + // // button14 // this.button14.Location = new System.Drawing.Point(13, 45); @@ -1039,21 +1130,18 @@ this.button13.UseVisualStyleBackColor = true; this.button13.Click += new System.EventHandler(this.button13_Click); // - // buttonRefreshRates + // contextMenuStrip1 // - this.buttonRefreshRates.Location = new System.Drawing.Point(10, 172); - this.buttonRefreshRates.Name = "buttonRefreshRates"; - this.buttonRefreshRates.Size = new System.Drawing.Size(128, 23); - this.buttonRefreshRates.TabIndex = 4; - this.buttonRefreshRates.Text = "RefreshRates"; - this.buttonRefreshRates.UseVisualStyleBackColor = true; - this.buttonRefreshRates.Click += new System.EventHandler(this.buttonRefreshRates_Click); + this.contextMenuStrip1.Name = "contextMenuStrip1"; + this.contextMenuStrip1.Size = new System.Drawing.Size(61, 4); + this.contextMenuStrip1.Text = "Clear"; // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(638, 659); + this.ClientSize = new System.Drawing.Size(713, 659); + this.ContextMenuStrip = this.contextMenuStrip1; this.Controls.Add(this.listBoxEventLog); this.Controls.Add(this.tabControl1); this.Controls.Add(this.groupBox2); @@ -1175,6 +1263,12 @@ private System.Windows.Forms.Button button13; private System.Windows.Forms.Button button14; private System.Windows.Forms.Button buttonRefreshRates; + private System.Windows.Forms.Button button15; + private System.Windows.Forms.Button button16; + private System.Windows.Forms.ComboBox comboBox1; + private System.Windows.Forms.ComboBox comboBox2; + private System.Windows.Forms.Button button17; + private System.Windows.Forms.ContextMenuStrip contextMenuStrip1; } } diff --git a/TestApiClientUI/Form1.cs b/TestApiClientUI/Form1.cs index 1da7951d..5bb34d0d 100755 --- a/TestApiClientUI/Form1.cs +++ b/TestApiClientUI/Form1.cs @@ -11,6 +11,7 @@ using System.IO; using MtApi; using System.Net; using System.Diagnostics; +using System.Threading.Tasks; namespace TestApiClientUI { @@ -32,6 +33,9 @@ namespace TestApiClientUI apiClient.ConnectionStateChanged += apiClient_ConnectionStateChanged; initOrderCommandsGroup(); + + comboBox1.SelectedIndex = 0; + comboBox2.SelectedIndex = 0; } private void initOrderCommandsGroup() @@ -62,14 +66,14 @@ namespace TestApiClientUI GroupOrderCommands.Add(new PerformCommandHandler(orderType)); } - private void RunOnUIThread(Action action) + private void RunOnUiThread(Action action) { this.BeginInvoke(action); } private void apiClient_ConnectionStateChanged(object sender, MtConnectionEventArgs e) { - RunOnUIThread(() => + RunOnUiThread(() => { toolStripStatusConnection.Text = e.Status.ToString(); }); @@ -77,11 +81,11 @@ namespace TestApiClientUI switch (e.Status) { case MtConnectionState.Connected: - RunOnUIThread(onConnected); + RunOnUiThread(onConnected); break; case MtConnectionState.Disconnected: case MtConnectionState.Failed: - RunOnUIThread(onDisconnected); + RunOnUiThread(onDisconnected); break; } } @@ -90,7 +94,7 @@ namespace TestApiClientUI { string instrument = e.Quote.Instrument; - RunOnUIThread(() => + RunOnUiThread(() => { removeQuote(e.Quote); }); @@ -98,7 +102,7 @@ namespace TestApiClientUI private void apiClient_QuoteAdded(object sender, MtQuoteEventArgs e) { - RunOnUIThread(() => + RunOnUiThread(() => { addNewQuote(e.Quote); }); @@ -217,7 +221,7 @@ namespace TestApiClientUI { int orderId = apiClient.OrderSend(symbol, command, volume, price, slippage, stoploss, takeprofit, comment, magic, expiration, arrow_color); - RunOnUIThread(() => + RunOnUiThread(() => { if (orderId >= 0) { @@ -542,9 +546,12 @@ namespace TestApiClientUI private void addToLog(string msg) { - listBoxEventLog.Items.Add(msg); - listBoxEventLog.SetSelected(listBoxEventLog.Items.Count - 1, true); - listBoxEventLog.SetSelected(listBoxEventLog.Items.Count - 1, false); + RunOnUiThread(() => + { + listBoxEventLog.Items.Add(msg); + listBoxEventLog.SetSelected(listBoxEventLog.Items.Count - 1, true); + listBoxEventLog.SetSelected(listBoxEventLog.Items.Count - 1, false); + }); } private void button3_Click(object sender, EventArgs e) @@ -857,5 +864,117 @@ namespace TestApiClientUI var retVal = apiClient.RefreshRates(); addToLog(string.Format("RefreshRates result: {0}", retVal)); } + + private void button15_Click(object sender, EventArgs e) + { + for (var i = 0; i < 4; i++) + { + var ticket = i; + Task.Factory.StartNew(() => + { + MtOrder order = null; + try + { + order = apiClient.GetOrder(ticket, OrderSelectMode.SELECT_BY_POS, OrderSelectSource.MODE_TRADES); + } + catch (MtConnectionException ex) + { + addToLog("MtExecutionException: " + ex.Message); + } + catch (MtExecutionException ex) + { + addToLog("MtExecutionException: " + ex.Message); + } + + string result; + if (order != null) + { + result = + string.Format( + "Order: Ticket = {0}, Symbol = {1}, Operation = {2}, OpenPrice = {3}, ClosePrice = {4}, Lots = {5}, Profit = {6}, Comment = {7}, Commission = {8}, MagicNumber = {9}, OpenTime = {10}, CloseTime = {11}", + order.Ticket, order.Symbol, order.Operation, order.OpenPrice, order.ClosePrice, order.Lots, order.Profit, order.Comment, order.Commission, order.MagicNumber, order.OpenTime, order.CloseTime); + } + else + { + result = "Order is null"; + } + + addToLog(result); + }); + } + } + + private void button16_Click(object sender, EventArgs e) + { + var ticket = int.Parse(textBoxIndexTicket.Text); + var selectMode = (OrderSelectMode) comboBox1.SelectedIndex; + var selectSource = (OrderSelectSource) comboBox2.SelectedIndex; + + MtOrder order = null; + try + { + order = apiClient.GetOrder(ticket, selectMode, selectSource); + } + catch (MtConnectionException ex) + { + addToLog("MtExecutionException: " + ex.Message); + return; + } + catch (MtExecutionException ex) + { + addToLog("MtExecutionException: " + ex.Message + "; ErrorCode = " + ex.ErrorCode); + return; + } + + if (order == null) + { + addToLog("Order is null"); + return; + } + + var result = + string.Format( + "Order: Ticket = {0}, Symbol = {1}, Operation = {2}, OpenPrice = {3}, ClosePrice = {4}, Lots = {5}, Profit = {6}, Comment = {7}, Commission = {8}, MagicNumber = {9}, OpenTime = {10}, CloseTime = {11}", + order.Ticket, order.Symbol, order.Operation, order.OpenPrice, order.ClosePrice, order.Lots, order.Profit, order.Comment, order.Commission, order.MagicNumber, order.OpenTime, order.CloseTime); + addToLog(result); + } + + private void button17_Click(object sender, EventArgs e) + { + var selectSource = (OrderSelectSource)comboBox2.SelectedIndex; + + + IEnumerable orders = null; + try + { + orders = apiClient.GetOrders(selectSource); + } + catch (MtConnectionException ex) + { + addToLog("MtExecutionException: " + ex.Message); + return; + } + catch (MtExecutionException ex) + { + addToLog("MtExecutionException: " + ex.Message + "; ErrorCode = " + ex.ErrorCode); + return; + } + + if (orders == null) + { + addToLog("Orders is null"); + return; + + } + + foreach (var order in orders) + { + var result = + string.Format( + "Order: Ticket = {0}, Symbol = {1}, Operation = {2}, OpenPrice = {3}, ClosePrice = {4}, Lots = {5}, Profit = {6}, Comment = {7}, Commission = {8}, MagicNumber = {9}, OpenTime = {10}, CloseTime = {11}", + order.Ticket, order.Symbol, order.Operation, order.OpenPrice, order.ClosePrice, order.Lots, order.Profit, order.Comment, order.Commission, order.MagicNumber, order.OpenTime, order.CloseTime); + addToLog(result); + } + } } } diff --git a/TestApiClientUI/Form1.resx b/TestApiClientUI/Form1.resx index a97b9fce..47bb78b4 100755 --- a/TestApiClientUI/Form1.resx +++ b/TestApiClientUI/Form1.resx @@ -120,4 +120,7 @@ 17, 17 + + 133, 17 + \ No newline at end of file diff --git a/mq4/MtApi.ex4 b/mq4/MtApi.ex4 index bb54353d..838f11fd 100755 Binary files a/mq4/MtApi.ex4 and b/mq4/MtApi.ex4 differ diff --git a/mq4/MtApi.mq4 b/mq4/MtApi.mq4 index 26596ef8..aa7c0f6d 100755 --- a/mq4/MtApi.mq4 +++ b/mq4/MtApi.mq4 @@ -3,6 +3,7 @@ #include #include +#include #import "MTConnector.dll" bool initExpert(int expertHandle, int port, string symbol, double bid, double ask, string& err); @@ -40,6 +41,7 @@ string subjectValue; string some_textValue; string nameValue; string prefix_nameValue; +string requestValue; int barsCount; int priceCount; @@ -126,17 +128,18 @@ double myAsk; int preinit() { - message = "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" + ""; - symbolValue = "222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222" + ""; - commentValue = "333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333" + ""; - msgValue = "444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444" + ""; - captionValue = "555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555" + ""; - filenameValue = "666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666" + ""; - ftp_pathValue = "777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777" + ""; - subjectValue = "888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888" + ""; - some_textValue = "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" + ""; - nameValue = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ""; - prefix_nameValue = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + ""; + StringInit(message,200,0); + StringInit(symbolValue,200,0); + StringInit(commentValue,200,0); + StringInit(msgValue,200,0); + StringInit(captionValue,200,0); + StringInit(filenameValue,200,0); + StringInit(ftp_pathValue,200,0); + StringInit(subjectValue,200,0); + StringInit(some_textValue,200,0); + StringInit(nameValue,200,0); + StringInit(prefix_nameValue,200,0); + StringInit(requestValue, 500, "\0"); return (0); } @@ -232,7 +235,12 @@ int executeCommand() return (0); } - int commandType = pCommandType; + int commandType = pCommandType; + + if (commandType > 0) + { + Print("executeCommand: commnad type = ", commandType); + } switch (commandType) { @@ -240,6 +248,24 @@ int executeCommand() //NoCommand break; + case 155: //SignalServiceCommand + { + if (!getStringValue(ExpertHandle, 0, requestValue)) + { + PrintParamError("Signal"); + } + + string response = ""; + + if (requestValue != "") + { + Print("executeCommand: incoming signal = ", requestValue); + response = OnRequest(requestValue); + } + + sendStringResponse(ExpertHandle, response); + } + break; case 1: // OrderSend if (!getStringValue(ExpertHandle, 0, symbolValue)) { @@ -3502,7 +3528,7 @@ int executeCommand() Print("Unknown command type = ", commandType); sendVoidResponse(ExpertHandle); break; - } + } return (commandType); } @@ -3556,4 +3582,156 @@ bool OrderCloseAll() } return (true); -} \ No newline at end of file +} + +string CreateErrorResponse(int code, string message) +{ + JSONObject *joResponse = new JSONObject(); + joResponse.put("ErrorCode", new JSONNumber(code)); + joResponse.put("ErrorMessage", new JSONString(message)); + + string result = joResponse.toString(); + delete joResponse; + return result; +} + +string CreateSuccessResponse(string responseName, JSONValue* responseBody) +{ + JSONObject *joResponse = new JSONObject(); + joResponse.put("ErrorCode", new JSONNumber(ERR_NO_ERROR)); + + if (responseBody != NULL) + { + joResponse.put(responseName, responseBody); + } + + string result = joResponse.toString(); + delete joResponse; + return result; +} + +string OnRequest(string json) +{ + string response = ""; + + JSONParser *parser = new JSONParser(); + JSONValue *jv = parser.parse(json); + + if(jv == NULL) + { + Print("OnRequest [ERROR]:" + (string)parser.getErrorCode() + parser.getErrorMessage()); + } + else + { + if(jv.isObject()) + { + JSONObject *jo = jv; + int requestType = jo.getInt("RequestType"); + JSONObject *joRequest = jo.getObject("Request"); + + if (joRequest != NULL) + { + Print("OnRequest: RequestType = ", requestType); + + switch(requestType) + { + case 1: //GetOrder + response = ExecuteRequestGetOrder(joRequest); + break; + case 2: //GetOrders + response = ExecuteRequestGetOrders(joRequest); + break; + default: + Print("OnRequest [WARNING]: Unknown request type ", requestType); + response = CreateErrorResponse(-1, "Unknown request type"); + break; + } + } + else + { + Print("OnRequest [WARNING]: Request field is not defined"); + response = CreateErrorResponse(-1, "Request field is not defined"); + } + } + + delete jv; + } + + delete parser; + + Print("OnRequest: Response = ", response); + + return response; +} + +JSONObject* GetOrderJson(int index, int select, int pool) +{ + if (OrderSelect(index, select, pool) == false) + { + return NULL; + } + + int ticket = OrderTicket(); + string symbol = OrderSymbol(); + int operation = OrderType(); + double openPrice = OrderOpenPrice(); + double closePrice = OrderClosePrice(); + double lots = OrderLots(); + double profit = OrderProfit(); + string comment = OrderComment(); + double commission = OrderCommission(); + int magicNumber = OrderMagicNumber(); + datetime openTime = OrderOpenTime(); + datetime closeTime = OrderCloseTime(); + + JSONObject *joOrder = new JSONObject(); + joOrder.put("Ticket", new JSONNumber(ticket)); + joOrder.put("Symbol", new JSONString(symbol)); + joOrder.put("Operation", new JSONNumber(operation)); + joOrder.put("OpenPrice", new JSONNumber(openPrice)); + joOrder.put("ClosePrice", new JSONNumber(closePrice)); + joOrder.put("Lots", new JSONNumber(lots)); + joOrder.put("Profit", new JSONNumber(profit)); + joOrder.put("Comment", new JSONString(comment)); + joOrder.put("Commission", new JSONString(commission)); + joOrder.put("MagicNumber", new JSONNumber(magicNumber)); + joOrder.put("Commission", new JSONString(commission)); + joOrder.put("MagicNumber", new JSONNumber(magicNumber)); + joOrder.put("MtOpenTime", new JSONNumber(openTime)); + joOrder.put("MtCloseTime", new JSONNumber(closeTime)); + + return joOrder; +} + +string ExecuteRequestGetOrder(JSONObject *jo) +{ + int index = jo.getInt("Index"); + int select = jo.getInt("Select"); + int pool = jo.getInt("Pool"); + + Print("ExecuteRequestGetOrder: Index = ", index, "; Select = ", select, "; Pool = ", pool); + + JSONObject* joOrder = GetOrderJson(index, select, pool); + if (joOrder == NULL) + return CreateErrorResponse(GetLastError(), "GetOrder failed"); + return CreateSuccessResponse("Order", joOrder); +} + +string ExecuteRequestGetOrders(JSONObject *jo) +{ + int pool = jo.getInt("Pool"); + + Print("ExecuteRequestGetOrders: Pool = ", pool); + + int total=OrdersTotal(); + JSONArray* joOrders = new JSONArray(); + for(int pos = 0; pos < total; pos++) + { + JSONObject* joOrder = GetOrderJson(pos, SELECT_BY_POS, pool); + if (joOrder == NULL) + return CreateErrorResponse(GetLastError(), "GetOrders failed"); + joOrders.put(pos, joOrder); + } + + return CreateSuccessResponse("Orders", joOrders); +} diff --git a/mq4/hash.mqh b/mq4/hash.mqh new file mode 100644 index 00000000..a6f69c94 --- /dev/null +++ b/mq4/hash.mqh @@ -0,0 +1,672 @@ +// $Id: hash.mqh 125 2014-03-03 08:38:32Z ydrol $ +#ifndef YDROL_HASH_MQH +#define YDROL_HASH_MQH + +//#property strict + +/* + This is losely ported from a C version I have which was in turn modified from hashtable.c by Christopher Clark. + Copyright (C) 2014, Andrew Lord (NICKNAME=lordy) + Copyright (C) 2002, 2004 Christopher Clark + + 2014/02/21 - Readded PrimeNumber sizes and auto rehashing when load factor hit. +*/ + + + +/// Any value stored in a Hash must be a subclass of HashValue +class HashValue { +}; + +/// Linked list of values - there will be one list for each hash value +class HashEntry { + public: + string _key; + HashValue * _val; + HashEntry *_next; + + HashEntry() { + _key=NULL; + _val=NULL; + _next=NULL; + } + + HashEntry(string key,HashValue* val) { + _key=key; + _val=val; + _next=NULL; + } + + ~HashEntry() { + } +}; + +/// Convenience class for storing strings as hash values. +class HashString : public HashValue { + private: + string val; + public: + HashString(string v) { val=v;} + string getVal() { return val; } +}; + +/// Convenience class for storing doubles as hash values. +class HashDouble : public HashValue { + private: + double val; + public: + HashDouble(double v) { val=v;} + double getVal() { return val; } +}; + +/// Convenience class for storing ints as hash values. +class HashInt : public HashValue { + private: + int val; + public: + HashInt(int v) { val=v;} + int getVal() { return val; } +}; + +/// Convenience class for storing longs as hash values. +class HashLong : public HashValue { + private: + long val; + public: + HashLong(datetime v) { val=v;} + long getVal() { return val; } + +}; + +/// Convenience class for storing datetimes as hash values. +class HashDatetime : public HashValue { + private: + datetime val; + public: + HashDatetime(datetime v) { val=v;} + datetime getVal() { return val; } +}; + +/// +/// Hash class allows objects to be stored in a table index by strings. +/// the stored Objects must be a sub class of the HashValue class. +/// +/// There are some convenience classes to hold atomic types as values HashString,HashDouble,HashInt +/// +///EXAMPLE: +/// +///
+/// class myClass: public HashValue {
+///   public: int v;
+///   myClass(int a) { v = a;}
+/// };
+///
+/// // Create the objects as needed
+///
+///      myClass *a = new myClass(1);
+///      myClass *b = new myClass(2);
+///      myClass *c = new myClass(3);
+///
+/// // Then to insert into hash etc.
+///
+///      Hash* h = new Hash(193,true); 
+///      // 'true' means when the hash will adopt the values and delete them when they are removed from the hash or when the hash is deleted.
+///
+///      h.hPut("a",a);
+///      h.hPut("b",b);
+///      h.hPut("c",c);
+///
+///      myClass *d = h.hGet("b");
+///
+///      etc.
+///
+/// // Iterate over hash
+///    HashLoop *l
+///    for (l = new HashLoop(h) ; l.hasNext() ; l.next()  ) {
+///        string key = l.key();
+///        MyClass *c = l.val();
+///    }
+///    delete l;
+///
+///    // Delete from hash - This will also delete 'a' because we set the 'adopt' flag on the hash.
+///    h.hDel("a");
+///
+///    //Delete the hash - this will also delete 'b' and 'c' because of the adopt flag.
+///    delete h;
+/// 
+class Hash : public HashValue { + +private: + /// Number of slots in the hashtable. + /// this should be approx number of elements to store. Depending on hash algorithm + /// it may optimally be a prime or a power of two etc. but probably not important + /// for MQL4 performance. A future optimisation might be to move the hashcode function to a DLL?? + uint _hashSlots; + + /// Number of elements at which hash will get resized. + int _resizeThreshold; + + /// number of things in the hash + int _hashEntryCount; + + /// an array of linked lists (HashEntry). one for each hash value. + /// To store an object against a string(key) - get the string hashcode, then insert pair (key,val) into the linked list for that hashcode. + /// To fetch an object against a string(key) - get the string hashcode, get linked-list at that hashcode index, then search for the key and return the val. + HashEntry* _buckets[]; + + /// If true the hash will free(delete) values as they are removed, or at cleanup. + bool _adoptValues; + + int _errCode; + string _errText; + + void init(uint size,bool adoptValues) + { + _hashSlots = 0; + _hashEntryCount = 0; + clearError(); + setAdoptValues(adoptValues); + + rehash(size); + } + + // Hash table distribution is better when size is prime, eg if hash function procduces numbers + // that are multiples of x, then there may be grouping occuring around gcd(x,slots) gcd(2x,slots) etc + // using a prime size helps spread the distribution. + uint size2prime(uint size) { + int pmax=ArraySize(_primes); + for(int p=0 ; p= size) { + return _primes[p]; + } + } + return size; + } + + /// Primes that approx double in size, used for hash table sizes to avoid gcd causing bunching + static uint _primes[]; + + /// After reviewing quite a few hash functions I settled on the one below. + /// http://www.cse.yorku.ca/~oz/hash.html + /// this is the bottleneck function. Shame mql hash no default hash method for objects. + uint hash(string s) + { + + uchar c[]; + uint h = 0; + + if (s != NULL) { + h = 5381; + int n = StringToCharArray(s,c); + for(int i = 0 ; i < n ; i++ ) { + h = ((h << 5 ) + h ) + c[i]; + } + } + return h % _hashSlots; + } + void clearError() { + setError(0,""); + } + void setError(int e,string m) { + _errCode = e; + _errText = m; + //error((string)e,m); + } + +public: + + /// Constructor: Create a Hash Object + Hash() { + init(17,true); + } + + + /// Constructor: Create a Hash Object + /// @param adoptValues : If true the hash destructor will delete all dynamically allocated hash values. + Hash(bool adoptValues) { + init(17,adoptValues); + } + + /// Constructor: Create a Hash Object + /// @param size : Approximate size (actual size will be a larger prime number close to a power of 2) + /// @param adoptValues : If true the hash destructor will delete all dynamically allocated hash values. + Hash(int size,bool adoptValues) { + init(size,adoptValues); + } + + ~Hash() { + + // Free entries. + for(uint i = 0 ; i< _hashSlots ; i++) { + HashEntry *nextEntry = NULL; + for(HashEntry *entry = _buckets[i] ; entry!= NULL ; entry = nextEntry ) + { + nextEntry = entry._next; + + if (_adoptValues && entry._val != NULL && CheckPointer(entry._val) == POINTER_DYNAMIC ) { + delete entry._val; + } + delete entry; + } + _buckets[i] = NULL; + } + } + + /// Return any error that has occured. This should be used when + /// retriving values in a Hash that may contain NULLs. hGet() + /// methods can return NULL if not found, in which case getErrorCode + /// will be set. + int getErrCode() { + return _errCode; + } + /// Return text of the error message. + string getErrText() { + return _errText; + } + + /// If true the hash destructor will delete all dynamically allocated hash values. + void setAdoptValues(bool v) { + _adoptValues = v; + } + + /// True if the hash destructor will delete all dynamically allocated hash values. + bool getAdoptValues() { + return _adoptValues; + } + + private: + uint _foundIndex; // After find() is called is set to hashindex for name whether found or not. + HashEntry* _foundEntry; // After find() is called is set to the HashEntry that contains the key. + HashEntry* _foundPrev; // After find() is called is set to the HashEntry before the entry + // (could use double linked list but requires more memory). + + /// Look for the required entry for key 'name' true if found. + bool find(string keyName) { + + //Alert("finding"); + bool found = false; + + // Get the index using the hashcode of the string + _foundIndex = hash(keyName); + + + if (_foundIndex>_hashSlots ) { + + setError(1,"hGet: bad hashIndex="+(string)_foundIndex+" size "+(string)_hashSlots); + + } else { + + // Search the linked list determined by the index. + + for(HashEntry *e = _buckets[_foundIndex] ; e != NULL ; e = e._next ) { + if (e._key == keyName) { + _foundEntry = e; + found=true; + break; + } + // Track the item before the target item in case deleting from single linked list. + _foundPrev = e; + } + } + + return found; + } + + public: + + /// This is used by the HashLoop class to get start of LinkedList at bucket[i] + HashEntry*getEntry(int i) { + return _buckets[i]; + } + + /// Return the number of slots/buckets (not number of elements) + uint getSlots() { + return _hashSlots; + } + /// Return the number of elements in the Hash + int getCount() { + return _hashEntryCount; + } + + /// Change the hash size and re-allocate values to new buckets. + bool rehash(uint newSize) { + bool ret = false; + HashEntry* oldTable[]; + + uint oldSize = _hashSlots; + newSize = size2prime(newSize); + //info("rehashing from "+(string)_hashSlots+" to "+(string)newSize+" "+(string)GetTickCount()); + + if (newSize <= getSlots()) { + setError(2,"rehash "+(string)newSize+" <= "+(string)_hashSlots); + } else if (ArrayResize(_buckets,newSize) != newSize) { + setError(3,"unable to resize "); + } else if (ArrayResize(oldTable,oldSize) != oldSize) { + setError(4,"unable to resize old copy "); + } else { + //Copy old table. + uint i; + for(i = 0 ; i < oldSize ; i++ ) oldTable[i] = _buckets[i]; + // Init new entries - not sure if MQL does this anyway + for(i = 0 ; ikeyName key. This will overwrite any existing + /// value. It adoptValues is set, it will also free the value if applicable. + /// @param keyName : key name + /// @param obj : Value to store + /// @return the previous value of the key or NULL if there wasnt one + HashValue *hPut(string keyName,HashValue *obj) { + + HashValue *ret = NULL; + clearError(); + + if (find(keyName)) { + // Return revious value + ret = _foundEntry._val; + /* + // Replace entry contents + if (_adoptValues && _foundEntry._val != NULL && CheckPointer(_foundEntry._val) == POINTER_DYNAMIC ) { + delete _foundEntry._val; + } + */ + _foundEntry._val = obj; + + } else { + // Insert new entry at head of list + HashEntry* e = new HashEntry(keyName,obj); + HashEntry* first = _buckets[_foundIndex]; + e._next = first; + _buckets[_foundIndex] = e; + _hashEntryCount++; + + //info((string)_hashEntryCount+" vs. "+(string)_resizeThreshold); + // Auto Resize if number of entries hits _resizeThreshold + if (_hashEntryCount > _resizeThreshold ) { + rehash(_hashSlots/2*3); // this will snap to the next prime + } + } + return ret; + } + /// Store a string as hash value (HashString) + /// @return the previous value of the key or NULL if there wasnt one + HashValue* hPutString(string keyName,string s) { + HashString *v = new HashString(s); + return hPut(keyName,v); + } + /// Store a double as hash value (HashDouble) + /// @return the previous value of the key or NULL if there wasnt one + HashValue* hPutDouble(string keyName,double d) { + HashDouble *v = new HashDouble(d); + return hPut(keyName,v); + } + /// Store an int as hash value (HashInt) + /// @return the previous value of the key or NULL if there wasnt one + HashValue* hPutInt(string keyName,int i) { + HashInt *v = new HashInt(i); + return hPut(keyName,v); + } + + /// Store a datetime as hash value (HashLong) + /// @return the previous value of the key or NULL if there wasnt one + HashValue* hPutLong(string keyName,long i) { + HashLong *v = new HashLong(i); + return hPut(keyName,v); + } + + /// Store a datetime as hash value (HashDatetime) + /// @return the previous value of the key or NULL if there wasnt one + HashValue* hPutDatetime(string keyName,datetime i) { + HashDatetime *v = new HashDatetime(i); + return hPut(keyName,v); + } + + /// Delete an entry from the hash. + bool hDel(string keyName) { + + bool found = false; + clearError(); + + if (find(keyName)) { + HashEntry *next = _foundEntry._next; + if (_foundPrev != NULL) { + //Remove entry from the middle of the list. + _foundPrev._next = next; + } else { + // remove from head of list + _buckets[_foundIndex] = next; + } + + if (_adoptValues && _foundEntry._val != NULL&& CheckPointer(_foundEntry._val) == POINTER_DYNAMIC) { + delete _foundEntry._val; + } + delete _foundEntry; + _hashEntryCount--; + found=true; + + } + return found; + } +}; +uint Hash::_primes[] = { + 17, 53, 97, 193, 389, + 769, 1543, 3079, 6151, + 12289, 24593, 49157, 98317, + 196613, 393241, 786433, 1572869, + 3145739, 6291469, 12582917, 25165843, + 50331653, 100663319, 201326611, 402653189, + 805306457, 1610612741}; + +/// Class to iterate over a Hash using ... +///
+///   HashLoop *l
+///   for (l = new HashLoop(h) ; l.hasNext() ; l.next()  ) {
+///       string key = l.key();
+///       MyClass *c = l.val();
+///   }
+///   delete l;
+/// 
+class HashLoop { + private: + uint _index; + HashEntry *_currentEntry; + Hash *_hash; + + public: + /// Create iterator for a hash - move to first item + HashLoop(Hash *h) { + setHash(h); + } + ~HashLoop() {}; + + /// Clear current state and move to first item (if any). + void reset() { + _index=0; + _currentEntry = _hash.getEntry(_index); + + // Move to first item + if (_currentEntry == NULL) { + next(); + } + } + + /// Change the hash over which to iterate. + void setHash(Hash *h) { + _hash = h; + reset(); + } + + /// Check if more items. + bool hasNext() { + bool ret = ( _currentEntry != NULL); + //config("hasNext=",ret); + return ret; + } + + /// Move to next item. + void next() { + + //config("next : index = ",_index); + + // Advance + if (_currentEntry != NULL) { + _currentEntry = _currentEntry._next; + } + + // Keep advancing if _currentEntry is null + while (_currentEntry==NULL) { + _index++; + if (_index >= _hash.getSlots() ) return ; + _currentEntry = _hash.getEntry(_index); + } + } + + /// Return the key name of the current item. + string key() { + if (_currentEntry != NULL) { + return _currentEntry._key; + } else { + return NULL; + } + } + + /// Return the value. + HashValue *val() { + if (_currentEntry != NULL) { + return _currentEntry._val; + } else { + return NULL; + } + } + + /// Convenience functions for retriving int from a current HashInt entry + int valInt() { + return ((HashInt *)val()).getVal(); + } + + /// Convenience functions for retriving int from a current HashString entry + string valString() { + return ((HashString *)val()).getVal(); + } + + /// Convenience functions for retriving int from a current HashDouble entry + double valDouble() { + return ((HashDouble *)val()).getVal(); + } + + /// Convenience functions for retriving int from a current HashLong entry + long valLong() { + return ((HashLong *)val()).getVal(); + } + /// Convenience functions for retriving int from a current HashDatetime entry + datetime valDatetime() { + return ((HashDatetime *)val()).getVal(); + } +}; + + +#endif diff --git a/mq4/json.mqh b/mq4/json.mqh new file mode 100644 index 00000000..8c391c72 --- /dev/null +++ b/mq4/json.mqh @@ -0,0 +1,1013 @@ +// $Id: json.mqh 118 2014-02-28 23:50:37Z ydrol $ +#ifndef YDROL_JSON_MQH +#define YDROL_JSON_MQH + +#include + +// (C)2014 Andrew Lord forex@NICKNAME@lordy.org.uk +// Parse a JSON String - Adapted for mql4++ from my gawk implementation +// ( https://code.google.com/p/oversight/source/browse/trunk/bin/catalog/json.awk ) + +/* + TODO the constants true|false|null could be represented as fixed objects. + To do this the deleting of _hash and _array must skip these objects. + + TODO test null + + TODO Parse Unicode Escape +*/ + +/* + See json_demo for examples. + + This requires the hash.mqh ( http://codebase.mql4.com/9238 , http://lordy.co.nf/hash ) + + + + */ + +/// Different types of JSON Values +enum ENUM_JSON_TYPE { JSON_NULL,JSON_OBJECT,JSON_ARRAY,JSON_NUMBER,JSON_STRING,JSON_BOOL }; + +class JSONString; +/// +/// Generic class for all JSON types (Number, String, Bool, Array, Object ) +/// It is a subclass of HashValue so it can be stored in an JSONObject hash +/// +class JSONValue : public HashValue + { +private: + ENUM_JSON_TYPE _type; + +public: + JSONValue() {} + ~JSONValue() {} + ENUM_JSON_TYPE getType() { return _type; } + void setType(ENUM_JSON_TYPE t) { _type=t; } + + /// True if JSONValue is a instance of JSONString + bool isString() { return _type==JSON_STRING; } + + /// True if JSONValue is a instance of JSONNull + bool isNull() { return _type==JSON_NULL; } + + /// True if JSONValue is a instance of JSONObject + bool isObject() { return _type==JSON_OBJECT; } + + /// True if JSONValue is a instance of JSONArray + bool isArray() { return _type==JSON_ARRAY; } + + /// True if JSONValue is a instance of JSONNumber + bool isNumber() { return _type==JSON_NUMBER; } + + /// True if JSONValue is a instance of JSONBool + bool isBool() { return _type==JSON_BOOL; } + + // Override in child classes + virtual string toString() + { + return ""; + } + + // Some convenience getters to cast to the subtype. - this is bad OO design! + + /// If this JSONValue is an instance of JSONString return the string (or cast will fail) + string getString() { return((JSONString *)GetPointer(this)).getString(); } + + /// If this JSONValue is an instance of JSONNumber return the double (or cast will fail) + double getDouble() { return((JSONNumber *)GetPointer(this)).getDouble(); } + + /// If this JSONValue is an instance of JSONNumber return the long (or cast will fail) + long getLong() { return((JSONNumber *)GetPointer(this)).getLong(); } + + /// If this JSONValue is an instance of JSONNumber return the int (or cast will fail) + int getInt() { return((JSONNumber *)GetPointer(this)).getInt(); } + + /// If this JSONValue is an instance of JSONBool return the bool (or cast will fail) + bool getBool() { return((JSONBool *)GetPointer(this)).getBool(); } + + /// Get the string value of the JSONValue, without Program termination + /// @param val : String object from which value will be extracted. + /// @param out : The string than was extracted. + /// @return true if OK else false + static bool getString(JSONValue *val,string &out) + { + if(val!=NULL && val.isString()) + { + out = val.getString(); + return true; + } + return false; + } + /// Get the bool value of the JSONValue, without Program termination + /// @param val : String object from which value will be extracted. + /// @param out : The bool than was extracted. + /// @return true if OK else false + static bool getBool(JSONValue *val,bool &out) + { + if(val!=NULL && val.isBool()) + { + out = val.getBool(); + return true; + } + return false; + } + /// Get the double value of the JSONValue, without Program termination + /// @param val : String object from which value will be extracted. + /// @param out : The double than was extracted. + /// @return true if OK else false + static bool getDouble(JSONValue *val,double &out) + { + if(val!=NULL && val.isNumber()) + { + out = val.getDouble(); + return true; + } + return false; + } + /// Get the long value of the JSONValue, without Program termination + /// @param val : String object from which value will be extracted. + /// @param out : The long than was extracted. + /// @return true if OK else false + static bool getLong(JSONValue *val,long &out) + { + if(val!=NULL && val.isNumber()) + { + out = val.getLong(); + return true; + } + return false; + } + /// Get the int value of the JSONValue, without Program termination + /// @param val : String object from which value will be extracted. + /// @param out : The int than was extracted. + /// @return true if OK else false + static bool getInt(JSONValue *val,int &out) + { + if(val!=NULL && val.isNumber()) + { + out = val.getInt(); + return true; + } + return false; + } + }; +// ----------------------------------------- + +/// Class to represent a JSON String +class JSONString : public JSONValue + { +private: + string _string; +public: + JSONString(string s) + { + setString(s); + setType(JSON_STRING); + } + JSONString() + { + setType(JSON_STRING); + } + string getString() { return _string; } + void setString(string v) { _string=v; } + string toString() { return StringConcatenate("\"",_string,"\""); } + }; +// ----------------------------------------- + +/// Class to represent a JSON Bool +class JSONBool : public JSONValue + { +private: + bool _bool; +public: + JSONBool(bool b) + { + setBool(b); + setType(JSON_BOOL); + } + JSONBool() + { + setType(JSON_BOOL); + } + bool getBool() { return _bool; } + void setBool(bool v) { _bool=v; } + string toString() { return(string)_bool; } + + }; +// ----------------------------------------- + +/// A JSON number may be internall replresented as either an MQL4 double or a long depending on how it was parsed. +/// If one type is set the other is zeroed. +class JSONNumber : public JSONValue + { +private: + long _long; + double _dbl; +public: + JSONNumber(long l) + { + _long= l; + _dbl = 0; + } + JSONNumber(double d) + { + _long= 0; + _dbl = d; + } + /// Get the long value, (cast) from internal double if necessary. + long getLong() + { + if(_dbl!=0) + { + return (long)_dbl; + } else { + return _long; + } + } + /// Get the int value, (cast) from internal value. + int getInt() + { + if(_dbl!=0) + { + return (int)_dbl; + } else { + return (int)_long; + } + } + /// Get the double value, (cast) from internal long if necessary. + double getDouble() + { + if(_long!=0) + { + return (double)_long; + } else { + return _dbl; + } + } + string toString() + { + // Favour the long + if(_long!=0) + { + return (string)_long; + } else { + return (string)_dbl; + } + } + }; +// ----------------------------------------- + +/// This class should not be necessary, but null is genrally infrequent so +/// I havent bothered to code it away yet. +class JSONNull : public JSONValue + { +public: + JSONNull() + { + setType(JSON_NULL); + } + ~JSONNull() {} + string toString() + { + return "null"; + } + }; + +//forward declaration +class JSONArray; +/// This represents a JSONObject which is represented internally as a Hash +class JSONObject : public JSONValue + { +private: + Hash *_hash; +public: + JSONObject() + { + setType(JSON_OBJECT); + } + ~JSONObject() + { + if(_hash != NULL) delete _hash; + } + /// Lookup key and get associated string value - halt program if wrong type(cast error) or doesnt exist(null pointer) + string getString(string key) + { + return getValue(key).getString(); + } + /// Lookup key and get associated bool value - halt program if wrong type(cast error) or doesnt exist(null pointer) + bool getBool(string key) + { + return getValue(key).getBool(); + } + /// Lookup key and get associated double value - halt program if wrong type(cast error) or doesnt exist(null pointer) + double getDouble(string key) + { + return getValue(key).getDouble(); + } + /// Lookup key and get associated long value - halt program if wrong type(cast error) or doesnt exist(null pointer) + long getLong(string key) + { + return getValue(key).getLong(); + } + /// Lookup key and get associated int value - halt program if wrong type(cast error) or doesnt exist(null pointer) + int getInt(string key) + { + return getValue(key).getInt(); + } + + /// Lookup key and get associated string value, return false if failure. + bool getString(string key,string &out) + { + return getString(getValue(key),out); + } + /// Lookup key and get associated bool value, return false if failure. + bool getBool(string key,bool &out) + { + return getBool(getValue(key),out); + } + /// Lookup key and get associated double value, return false if failure. + bool getDouble(string key,double &out) + { + return getDouble(getValue(key),out); + } + /// Lookup key and get associated long value, return false if failure. + bool getLong(string key,long &out) + { + return getLong(getValue(key),out); + } + /// Lookup key and get associated int value, return false if failure. + bool getInt(string key,int &out) + { + return getInt(getValue(key),out); + } + + /// Lookup key and get associated array, NULL if not present. Cast failure if not an Array. + JSONArray *getArray(string key) + { + return getValue(key); + } + /// Lookup key and get associated Object, NULL if not present. Cast failure if not an Object. + JSONObject *getObject(string key) + { + return getValue(key); + } + /// Lookup key and get associated value - best for data whose structure might change as any type can safely be returned. + JSONValue *getValue(string key) + { + if(_hash==NULL) + { + return NULL; + } + return (JSONValue*)_hash.hGet(key); + } + + /// Store the value against the specified key string - Used by the parser. + void put(string key,JSONValue *v) + { + if(_hash==NULL) _hash=new Hash(); + _hash.hPut(key,v); + } + string toString() + { + string s="{"; + if(_hash!=NULL) + { + HashLoop *l; + int n=0; + + for(l=new HashLoop(_hash); l.hasNext(); l.next()) + { + JSONValue *v=(JSONValue *)(l.val()); + s=StringConcatenate(s,(++n==1?"":","), + "\"",l.key(),"\" : ",v.toString()); + } + delete l; + } + s=s+"}"; + return s; + } + + /// Return the internal Hash - Used by JSONIterator + Hash *getHash() + { + return _hash; + } + }; +/// This is a JSONArray which is represented internally as a MQL4 dynamic array of JSONValue * +class JSONArray : public JSONValue + { +private: + int _size; + JSONValue *_array[]; +public: + JSONArray() + { + setType(JSON_ARRAY); + } + ~JSONArray() + { + // clean up array + for(int i=ArrayRange(_array,0)-1; i>=0; i--) + { + if(CheckPointer(_array[i]) == POINTER_DYNAMIC ) delete _array[i]; + } + } + // Getters for Objects (key lookup ) -------------------------------------- + + /// Lookup string value by array index - halt program if wrong type(cast error) or doesnt exist(null pointer) + string getString(int index) + { + return getValue(index).getString(); + } + /// Lookup bool value by array index - halt program if wrong type(cast error) or doesnt exist(null pointer) + bool getBool(int index) + { + return getValue(index).getBool(); + } + /// Lookup double value by array index - halt program if wrong type(cast error) or doesnt exist(null pointer) + double getDouble(int index) + { + return getValue(index).getDouble(); + } + /// Lookup long value by array index - halt program if wrong type(cast error) or doesnt exist(null pointer) + long getLong(int index) + { + return getValue(index).getLong(); + } + /// Lookup int value by array index - halt program if wrong type(cast error) or doesnt exist(null pointer) + int getInt(int index) + { + return getValue(index).getInt(); + } + + /// Lookup JSONString by array index. NULL if not present. Cast failure if not an Object. + bool getString(int index,string &out) + { + return getString(getValue(index),out); + } + /// Lookup JSONBool by array index. NULL if not present. Cast failure if not an Object. + bool getBool(int index,bool &out) + { + return getBool(getValue(index),out); + } + /// Lookup JSONNumber by array index. NULL if not present. Cast failure if not an Object. + bool getDouble(int index,double &out) + { + return getDouble(getValue(index),out); + } + /// Lookup JSONNumber by array index. NULL if not present. Cast failure if not an Object. + bool getLong(int index,long &out) + { + return getLong(getValue(index),out); + } + /// Lookup JSONNumber by array index. NULL if not present. Cast failure if not an Object. + bool getInt(int index,int &out) + { + return getInt(getValue(index),out); + } + + /// Lookup array child by index, NULL if not present. Cast failure if not an Array. + JSONArray *getArray(int index) + { + return getValue(index); + } + + /// Lookup object child by index, NULL if not present. Cast failure if not an Array. + JSONObject *getObject(int index) + { + return getValue(index); + } + /// The following method allows any type to be returned. Use this when parsing unpredictable data + JSONValue *getValue(int index) + { + return _array[index]; + } + + /// Used by the Parser when building the array + bool put(int index,JSONValue *v) + { + if(index>=_size) + { + int oldSize = _size; + int newSize = ArrayResize(_array,index+1,30); + if(newSize <= index) return false; + _size=newSize; + + // initialise + for(int i=oldSize; i0) + { + s=StringConcatenate(s,_array[0].toString()); + for(int i=1; i<_size; i++) + { + s=StringConcatenate(s,",",_array[i].toString()); + } + } + s=s+"]"; + return s; + } + + int size() + { + return _size; + } + }; +/// Parse JSON text using a simple recursive descent parser +/// Exmaple +/// +///
+///    string s = "{ \"firstName\": \"John\","+
+///       " \"lastName\": \"Smith\","+
+///       " \"age\": 25,"+
+///       " \"address\": { \"streetAddress\": \"21 2nd Street\", \"city\": \"New York\", \"state\": \"NY\", \"postalCode\": \"10021\" },"+
+///       " \"phoneNumber\": [ { \"type\": \"home\", \"number\": \"212 555-1234\" }, { \"type\": \"fax\", \"number\": \"646 555-4567\" } ],"+
+///       " \"gender\":{ \"type\":\"male\" }  }";
+///
+///    JSONParser *parser = new JSONParser();
+///
+///    JSONValue *jv = parser.parse(s);
+///
+///    if (jv == NULL) {
+///
+///        Print("error:"+(string)parser.getErrorCode()+parser.getErrorMessage());
+///
+///    } else {
+///
+///        Print("PARSED:"+jv.toString());
+///
+///        if (jv.isObject()) {
+///
+///            JSONObject *jo = jv;
+///
+///            // Direct access - will throw null pointer if wrong getter used.
+///
+///            Print("firstName:" + jo.getString("firstName"));
+///            Print("city:" + jo.getObject("address").getString("city"));
+///            Print("phone:" + jo.getArray("phoneNumber").getObject(0).getString("number"));
+///
+///            // Safe access in case JSON data is missing or different.
+///
+///            if (jo.getString("firstName",s) ) Print("firstName = "+s);
+///
+///            // Loop over object returning JSONValue
+///
+///            JSONIterator *it = new JSONIterator(jo);
+///            for( ; it.hasNext() ; it.next()) {
+///                Print("loop:"+it.key()+" = "+it.val().toString());
+///            }
+///            delete it;
+///        }
+///        delete jv;
+///    }
+///    delete parser;
+/// 
+ +class JSONParser + { +private: + /// Current parse position + int _pos; + /// The input string is expanded into an array of ushort (wchar) + ushort _in[]; + /// Length of string + int _len; + /// The original input string + string _instr; + + int _errCode; + string _errMsg; + + void setError(int code=1,string msg="unknown error") + { + _errCode|=code; + if(_errMsg=="") + { + _errMsg="JSONParser::Error "+msg; + } else { + _errMsg=StringConcatenate(_errMsg,"\n",msg); + } + } + + /// Parse a JSON Object + JSONObject *parseObject() + { + JSONObject *o=new JSONObject(); + skipSpace(); + if(expect('{')) + { + while(_errCode==0) + { + skipSpace(); + if(_in[_pos]!='"') break; + + // Read the key + string key=parseString(); + + if(_errCode!=0 || key==NULL) break; + + skipSpace(); + + if(!expect(':')) break; + + // read the value + JSONValue *v = parseValue(); + if(_errCode != 0 ) break; + + o.put(key,v); + + skipSpace(); + + if(!expectOptional(',')) break; + } + if(!expect('}')) + { + setError(2,"expected \" or } "); + } + } + if(_errCode!=0) + { + delete o; + o=NULL; + } + return o; + } + + bool isDigit(ushort c) + { + return (c >= '0' && c <= '9' ) || c == '+' || c == '-'; + } + + bool isDoubleDigit(ushort c) + { + return (c >= '0' && c <= '9' ) || c == '+' || c == '-' || c == '.' || c == 'e' || c == 'E'; + } + + void skipSpace() + { + while(_in[_pos]==' ' || _in[_pos]=='\t' || _in[_pos]=='\r' || _in[_pos]=='\n') + { + if(_pos>=_len) break; + _pos++; + } + } + + bool expect(ushort c) + { + bool ret=false; + if(c==_in[_pos]) + { + _pos++; + ret=true; + } else { + setError(1,StringConcatenate("expected ", + ShortToString(c),"(",c,")", + " got ",ShortToString(_in[_pos]),"(",_in[_pos],")")); + } + return ret; + } + + bool expectOptional(ushort c) + { + bool ret=false; + if(c==_in[_pos]) + { + _pos++; + ret=true; + } + return ret; + } + + string parseString() + { + string ret=""; + if(expect('"')) + { + while(true) + { + int end=_pos; + while(end<_len && _in[end]!='"' && _in[end]!='\\') + { + end++; + } + + if(end>=_len) + { + setError(2,"missing quote: end"+(string)end+":len"+(string)_len+":"+ShortToString(_in[_pos])+":"+StringSubstr(_instr,_pos,10)+"..."); + break; + } + // Check if character was escaped. + // TODO \" \\ \/ \b \f \n \r \t \u0000 + if(_in[end]=='\\') + { + // Add partial string and get more + ret=ret+StringSubstr(_instr,_pos,end-_pos); + end++; + if(end>=_len) + { + setError(4,"parse error after escape"); + } else { + ushort c=0; + switch(_in[end]) + { + case '"': + case '\\': + case '/': + c=_in[end]; + break; + case 'b': c=8; break; // backspace - 8 + case 'f': c=12; break; // form feed 12 + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + default: + setError(3,"unknown escape"); + } + if(c== 0) break; + ret = ret+ShortToString(c); + _pos= end+1; + } + } else if(_in[end]=='"') { + // End of string + ret=ret+StringSubstr(_instr,_pos,end-_pos); + _pos=end+1; + break; + } + } + } + if(_errCode!=0) + { + ret=NULL; + } + return ret; + } + + JSONValue *parseValue() + { + JSONValue *ret=NULL; + skipSpace(); + string s; + + if(_in[_pos]=='[') + { + + ret=(JSONValue*)parseArray(); + + } else if(_in[_pos]=='{') { + + ret=(JSONValue*)parseObject(); + + } else if(_in[_pos]=='"') { + + s=parseString(); + ret=(JSONValue*)new JSONString(s); + + } else if(isDoubleDigit(_in[_pos])) { + bool isDoubleOnly=false; + long l=0; + long sign; + // number + int i=_pos; + + if(_in[_pos]=='-') + { + sign=-1; + _pos++; + } else if(_in[_pos]=='+') { + sign=1; + _pos++; + } else { + sign=1; + } + + while(i<_len && isDigit(_in[i])) + { + l=l*10+(_in[i]-'0'); + i++; + } + if(isDoubleDigit(_in[i])) + { + // Looks like a real number; + while(i<_len && isDoubleDigit(_in[i])) + { + i++; + } + s=StringSubstr(_instr,_pos,i-_pos); + double d=sign*StringToDouble(s); + ret=(JSONValue*)new JSONNumber(d); // Create a Number as double only + } else { + l=sign*l; + ret=(JSONValue*)new JSONNumber(l); // Create a Number as a long + } + _pos=i; + + } else if(_in[_pos]=='t' && StringSubstr(_instr,_pos,4)=="true") { + + ret=(JSONValue*)new JSONBool(true); + _pos+=4; + + } else if(_in[_pos]=='f' && StringSubstr(_instr,_pos,5)=="false") { + + ret=(JSONValue*)new JSONBool(false); + _pos+=5; + + } else if(_in[_pos]=='n' && StringSubstr(_instr,_pos,4)=="null") { + + ret=(JSONValue*)new JSONNull(); + _pos+=4; + + } else { + + setError(3,"error parsing value at position "+(string)_pos); + + } + + if(_errCode!=0 && ret!=NULL) + { + delete ret; + ret=NULL; + } + return ret; + } + + JSONArray *parseArray() + { + JSONArray *ret=new JSONArray(); + + int index=0; + skipSpace(); + if(expect('[')) + { + while(_errCode==0) + { + skipSpace(); + + // read the value + JSONValue *v = parseValue(); + if(_errCode != 0) break; + + if(!ret.put(index++,v)) + { + setError(3,"memory error adding "+(string)index); + break; + } + + skipSpace(); + + if(!expectOptional(',')) break; + } + if(!expect(']')) + { + setError(2,"list: expected , or ] "); + } + } + + if(_errCode!=0) + { + delete ret; + ret=NULL; + } + return ret; + } +public: + int getErrorCode() + { + return _errCode; + } + string getErrorMessage() + { + return _errMsg; + } + /// Parse a sequnce of characters and return a JSONValue. + JSONValue *parse( + string s ///< Serialized JSON text + ) + { + int inLen; + JSONValue *ret=NULL; + StringTrimLeft(s); + StringTrimRight(s); + + _instr=s; + _len=StringToShortArray(_instr,_in); // nul '0' is added to length + _pos= 0; + _errCode= 0; + _errMsg = ""; + inLen=StringLen(_instr); + if(_len!=inLen+1 /* nul */) + { + setError(1,StringConcatenate("unable to create array ",inLen," got ",_len)); + } else { + _len --; + ret=parseValue(); + if(_errCode!=0) + { + _errMsg=StringConcatenate(_errMsg," at ",_pos," [",StringSubstr(_instr,_pos,10),"...]"); + } + } + return ret; + } + + }; +/// Class to iterate over a JSONObject (not a JSONArray) +class JSONIterator + { +private: + HashLoop*_l; + +public: + // Create iterator and move to first item + JSONIterator(JSONObject *jo) + { + _l=new HashLoop(jo.getHash()); + } + ~JSONIterator() + { + delete _l; + } + // Check if more items + bool hasNext() + { + return _l.hasNext(); + } + + // Move to next item + void next() + { + _l.next(); + } + + // Return item + JSONValue *val() + { + return (JSONValue *) (_l.val()); + } + + // Return key + string key() + { + return _l.key(); + } + + }; +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void json_demo() + { + string s="{ \"firstName\": \"John\","+ + " \"lastName\": \"Smith\","+ + " \"age\": 25,"+ + " \"address\": { \"streetAddress\": \"21 2nd Street\", \"city\": \"New York\", \"state\": \"NY\", \"postalCode\": \"10021\" },"+ + " \"phoneNumber\": [ { \"type\": \"home\", \"number\": \"212 555-1234\" }, { \"type\": \"fax\", \"number\": \"646 555-4567\" } ],"+ + " \"gender\":{ \"type\":\"male\" } }"; + + JSONParser *parser=new JSONParser(); + JSONValue *jv=parser.parse(s); + Print("json:"); + if(jv==NULL) + { + Print("error:"+(string)parser.getErrorCode()+parser.getErrorMessage()); + } else { + Print("PARSED:"+jv.toString()); + if(jv.isObject()) + { + JSONObject *jo=jv; + + // Direct access - will throw null pointer if wrong getter used. + Print("firstName:"+jo.getString("firstName")); + Print("city:"+jo.getObject("address").getString("city")); + Print("phone:"+jo.getArray("phoneNumber").getObject(0).getString("number")); + + // Safe access in case JSON data is missing or different. + if(jo.getString("firstName",s)) Print("firstName = "+s); + + // Loop over object returning JSONValue + JSONIterator *it=new JSONIterator(jo); + for(; it.hasNext(); it.next()) + { + Print("loop:"+it.key()+" = "+it.val().toString()); + } + delete it; + } + delete jv; + } + delete parser; + } + + +#endif +//+------------------------------------------------------------------+