1602 行
39 KiB
MQL5
1602 行
39 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
/*
|
|
# Especificación: Parser JSON de Alto Rendimiento para MQL5
|
|
|
|
Necesito que desarrolles un **parser JSON de alto rendimiento (High Performance JSON Parser)** completamente escrito en **MQL5 puro**.
|
|
|
|
## Objetivo principal
|
|
|
|
La prioridad absoluta es el **rendimiento**. El parser debe estar diseñado para ser lo más rápido posible sin sacrificar la compatibilidad con el estándar JSON.
|
|
|
|
No busco una implementación sencilla o académica; quiero una implementación optimizada para producción utilizando todas las técnicas de optimización que conozcas.
|
|
|
|
---
|
|
|
|
# Requisitos generales
|
|
|
|
* Todo el código debe estar contenido en **un único archivo `.mqh`**.
|
|
* No utilizar DLL.
|
|
* No utilizar librerías externas.
|
|
* No depender de componentes adicionales.
|
|
* Código 100% MQL5.
|
|
|
|
---
|
|
|
|
# Compatibilidad JSON
|
|
|
|
Debe soportar completamente la especificación JSON:
|
|
|
|
* Objetos (`{}`)
|
|
* Arrays (`[]`)
|
|
* Strings
|
|
* Numbers
|
|
|
|
* enteros
|
|
* decimales
|
|
* exponentes
|
|
* Boolean (`true`, `false`)
|
|
* `null`
|
|
* Secuencias de escape:
|
|
|
|
* `\"`
|
|
* `\\`
|
|
* `\/`
|
|
* `\b`
|
|
* `\f`
|
|
* `\n`
|
|
* `\r`
|
|
* `\t`
|
|
* `\uXXXX`
|
|
|
|
No quiero un parser parcial; debe aceptar cualquier JSON válido.
|
|
|
|
---
|
|
|
|
# Diseño de la arquitectura
|
|
|
|
Quiero que tú decidas la arquitectura interna.
|
|
|
|
Puedes utilizar cualquier técnica que mejore el rendimiento, por ejemplo:
|
|
|
|
* parser iterativo (evitar recursión si aporta velocidad)
|
|
* stacks manuales
|
|
* pools de memoria
|
|
* arenas de memoria
|
|
* estructuras contiguas
|
|
* tablas de índices
|
|
* minimización de asignaciones dinámicas
|
|
* reducción de copias de memoria
|
|
* acceso cache-friendly
|
|
* optimización de ramas (branch prediction)
|
|
* optimización de comparaciones de caracteres
|
|
* cualquier otra técnica de optimización que consideres adecuada.
|
|
|
|
No me interesa que la implementación sea "bonita"; me interesa que sea extremadamente rápida.
|
|
|
|
---
|
|
|
|
# Entrada de datos
|
|
|
|
El parser debe trabajar directamente sobre un buffer de bytes.
|
|
|
|
Ejemplo:
|
|
|
|
```cpp
|
|
uchar Buffer[];
|
|
```
|
|
|
|
La clase debe exponer públicamente este buffer para que yo pueda llenarlo directamente desde mi código sin copias adicionales.
|
|
|
|
Ejemplo:
|
|
|
|
```cpp
|
|
CJson json;
|
|
|
|
json.Buffer = MiBuffer;
|
|
|
|
json.Parse();
|
|
```
|
|
|
|
O una interfaz equivalente que no implique copiar datos.
|
|
|
|
---
|
|
|
|
# Clase principal
|
|
|
|
Quiero una clase similar a:
|
|
|
|
```cpp
|
|
class CJson
|
|
{
|
|
public:
|
|
|
|
uchar Buffer[];
|
|
|
|
bool Parse();
|
|
|
|
...
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
# API
|
|
|
|
Diseña una API limpia y orientada al rendimiento.
|
|
|
|
Debe incluir funciones útiles como:
|
|
|
|
* Parse()
|
|
* Clear()
|
|
* Reset()
|
|
* GetError()
|
|
* GetErrorMessage()
|
|
* HasError()
|
|
* IsObject()
|
|
* IsArray()
|
|
* GetType()
|
|
* Exists()
|
|
* Size()
|
|
* Count()
|
|
|
|
y cualquier otra función que facilite el uso del parser.
|
|
|
|
---
|
|
|
|
# Acceso a valores
|
|
|
|
Debe permitir acceder fácilmente a los nodos.
|
|
|
|
Ejemplos:
|
|
|
|
```cpp
|
|
json["symbol"];
|
|
|
|
json["price"];
|
|
|
|
json["ticks"][5];
|
|
|
|
json["account"]["balance"];
|
|
```
|
|
|
|
o cualquier interfaz equivalente que sea eficiente.
|
|
|
|
---
|
|
|
|
# Representación interna
|
|
|
|
Quiero que el parser almacene una representación eficiente del documento JSON.
|
|
|
|
No quiero que vuelva a parsear el texto cada vez que se consulta un valor.
|
|
|
|
La estructura interna debe permitir consultas rápidas.
|
|
|
|
---
|
|
|
|
# Manejo de memoria
|
|
|
|
La implementación debe minimizar:
|
|
|
|
* asignaciones dinámicas
|
|
* fragmentación
|
|
* copias innecesarias
|
|
* uso excesivo de memoria
|
|
|
|
Si es posible, reutilizar buffers mediante `Reset()` o `Clear()`.
|
|
|
|
---
|
|
|
|
# Manejo de errores
|
|
|
|
Debe disponer de un sistema completo de errores.
|
|
|
|
Por ejemplo:
|
|
|
|
```cpp
|
|
enum JsonError
|
|
{
|
|
JSON_OK,
|
|
JSON_UNEXPECTED_CHARACTER,
|
|
JSON_UNEXPECTED_END,
|
|
JSON_INVALID_NUMBER,
|
|
JSON_INVALID_STRING,
|
|
JSON_INVALID_ESCAPE,
|
|
JSON_INVALID_UNICODE,
|
|
JSON_EXPECTED_COLON,
|
|
JSON_EXPECTED_COMMA,
|
|
JSON_EXPECTED_OBJECT,
|
|
JSON_EXPECTED_ARRAY,
|
|
JSON_MAX_DEPTH,
|
|
...
|
|
};
|
|
```
|
|
|
|
Y funciones como:
|
|
|
|
```cpp
|
|
GetError();
|
|
|
|
GetErrorMessage();
|
|
|
|
GetErrorPosition();
|
|
|
|
GetLine();
|
|
|
|
GetColumn();
|
|
```
|
|
|
|
---
|
|
|
|
# Calidad del código
|
|
|
|
El código debe ser:
|
|
|
|
* modular
|
|
* documentado
|
|
* fácil de mantener
|
|
* altamente optimizado
|
|
* preparado para proyectos profesionales
|
|
|
|
---
|
|
|
|
# Optimización
|
|
|
|
Durante el desarrollo, prioriza siempre el rendimiento sobre la simplicidad.
|
|
|
|
Si una técnica mejora significativamente la velocidad aunque aumente la complejidad del código, prefiero la versión más rápida.
|
|
|
|
Quiero un parser que pueda competir en rendimiento con implementaciones de alto nivel adaptadas a las limitaciones de MQL5.
|
|
|
|
---
|
|
|
|
# Entregable
|
|
|
|
El resultado final debe ser un único archivo `.mqh`, completamente funcional, autocontenido y listo para incluir mediante:
|
|
|
|
```cpp
|
|
#include "Json.mqh"
|
|
```
|
|
|
|
sin depender de ningún otro archivo.
|
|
|
|
El objetivo final es obtener el parser JSON más rápido posible que pueda implementarse en MQL5 puro.
|
|
*/
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Json.mqh |
|
|
//| High Performance JSON Parser for MQL5 |
|
|
//| v1.0 - Pure MQL5 Implementation |
|
|
//+------------------------------------------------------------------+
|
|
// |
|
|
// CARACTERISTICAS |
|
|
// - Parser iterativo de una sola pasada sobre buffer uchar[] |
|
|
// - Almacenamiento contiguo de nodos (cache-friendly) |
|
|
// - Tabla de clasificacion de caracteres (O(1) por byte) |
|
|
// - Stacks manuales para profundidad (sin recursion del lenguaje) |
|
|
// - Strings referenciados por offset/length en el buffer original |
|
|
// - Numeros parseados y almacenados como double durante la tokenizacion |
|
|
// - Flag de escape detection para extraccion rapida de strings |
|
|
// - Sin DLL, sin librerias externas, sin dependencias |
|
|
// - API tipo documento + CJsonValue lightweight |
|
|
// |
|
|
//+------------------------------------------------------------------+
|
|
// USO BASICO |
|
|
// #include "Json.mqh" |
|
|
// |
|
|
// CJson json; |
|
|
// uchar buffer[] = ... ; // llenar buffer |
|
|
// json.Buffer = buffer; |
|
|
// if (json.Parse()) { |
|
|
// string s = json["name"].ToString(); |
|
|
// int i = json["count"].ToInt(); |
|
|
// double d = json["price"].ToDouble(); |
|
|
// bool b = json["active"].ToBool(); |
|
|
// int sz = json["items"].Count(); |
|
|
// string k = json["obj"].KeyAt(0); |
|
|
// string v = json["arr"][3].ToString(); |
|
|
// } else { |
|
|
// Print("Error: ", json.GetErrorMessage()); |
|
|
// } |
|
|
//+------------------------------------------------------------------+
|
|
#property strict
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| ENUM: Codigos de error |
|
|
//+------------------------------------------------------------------+
|
|
enum JsonError
|
|
{
|
|
JERR_OK = 0,
|
|
JERR_UNEXPECTED_CHAR = 1,
|
|
JERR_UNEXPECTED_END = 2,
|
|
JERR_INVALID_NUMBER = 3,
|
|
JERR_INVALID_STRING = 4,
|
|
JERR_INVALID_ESCAPE = 5,
|
|
JERR_INVALID_UNICODE = 6,
|
|
JERR_EXPECTED_COLON = 7,
|
|
JERR_EXPECTED_COMMA = 8,
|
|
JERR_EXPECTED_KEY = 9,
|
|
JERR_EXPECTED_VALUE = 10,
|
|
JERR_EXPECTED_OBJ_END = 11,
|
|
JERR_EXPECTED_ARR_END = 12,
|
|
JERR_DEPTH_EXCEEDED = 13,
|
|
JERR_NODE_LIMIT = 14,
|
|
JERR_TRAILING_DATA = 15,
|
|
JERR_NOT_PARSED = 16,
|
|
JERR_INDEX_OOB = 17,
|
|
JERR_KEY_NOT_FOUND = 18,
|
|
JERR_TYPE_MISMATCH = 19,
|
|
JERR_BUFFER_EMPTY = 20,
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| ENUM: Tipos de nodo JSON |
|
|
//+------------------------------------------------------------------+
|
|
enum JsonType
|
|
{
|
|
JT_NULL = 0,
|
|
JT_BOOL = 1,
|
|
JT_INT = 2,
|
|
JT_DOUBLE = 3,
|
|
JT_STRING = 4,
|
|
JT_ARRAY = 5,
|
|
JT_OBJECT = 6
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| FORWARD DECLARATIONS |
|
|
//+------------------------------------------------------------------+
|
|
class CJson;
|
|
class CJsonValue;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CONSTANTES DE CONFIGURACION |
|
|
//+------------------------------------------------------------------+
|
|
#define JSON_MAX_DEPTH 1024
|
|
#define JSON_INIT_NODES 8192
|
|
#define JSON_GROW_FACTOR 2
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| TABLA DE CLASIFICACION DE CARACTERES |
|
|
//+------------------------------------------------------------------+
|
|
#define _JC_CTRL 0
|
|
#define _JC_WS 1
|
|
#define _JC_QUOTE 2
|
|
#define _JC_DIGIT 3
|
|
#define _JC_MINUS 4
|
|
#define _JC_LIT 5
|
|
#define _JC_STRUCT 6
|
|
#define _JC_BSLASH 7
|
|
#define _JC_OTHER 8
|
|
|
|
static uchar s_char_class[256];
|
|
static bool s_class_init = false;
|
|
|
|
void Json_InitCharClass()
|
|
{
|
|
if(s_class_init) return;
|
|
s_class_init = true;
|
|
for(int i = 0; i < 256; i++)
|
|
{
|
|
uchar cls = _JC_OTHER;
|
|
if(i < 0x20)
|
|
cls = _JC_CTRL;
|
|
else if(i == ' ' || i == 0x09 || i == 0x0A || i == 0x0D)
|
|
cls = _JC_WS;
|
|
else if(i == '"')
|
|
cls = _JC_QUOTE;
|
|
else if(i >= '0' && i <= '9')
|
|
cls = _JC_DIGIT;
|
|
else if(i == '-')
|
|
cls = _JC_MINUS;
|
|
else if(i == 't' || i == 'f' || i == 'n')
|
|
cls = _JC_LIT;
|
|
else if(i == '{' || i == '}' || i == '[' || i == ']' || i == ':' || i == ',')
|
|
cls = _JC_STRUCT;
|
|
else if(i == '\\')
|
|
cls = _JC_BSLASH;
|
|
s_char_class[i] = cls;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| STRUCT: Nodo JSON interno |
|
|
//+------------------------------------------------------------------+
|
|
struct SJsonNode
|
|
{
|
|
int parent;
|
|
int child;
|
|
int next;
|
|
int key_off;
|
|
int key_len;
|
|
int val_off;
|
|
int val_len;
|
|
double num_val;
|
|
uchar type;
|
|
uchar flags;
|
|
ushort child_cnt;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CLASE: CJsonValue |
|
|
//+------------------------------------------------------------------+
|
|
class CJsonValue
|
|
{
|
|
protected:
|
|
CJson* m_doc;
|
|
int m_node;
|
|
|
|
public:
|
|
CJsonValue();
|
|
CJsonValue(CJson* doc, int node);
|
|
CJsonValue(const CJsonValue& other);
|
|
|
|
void operator=(const CJsonValue& other);
|
|
|
|
bool IsValid() const;
|
|
int NodeIndex() const;
|
|
|
|
bool IsNull() const;
|
|
bool IsBool() const;
|
|
bool IsInt() const;
|
|
bool IsDouble() const;
|
|
bool IsNumber() const;
|
|
bool IsString() const;
|
|
bool IsArray() const;
|
|
bool IsObject() const;
|
|
int GetType() const;
|
|
|
|
string ToString() const;
|
|
int ToInt() const;
|
|
double ToDouble() const;
|
|
bool ToBool() const;
|
|
|
|
CJsonValue operator[](string key) const;
|
|
CJsonValue operator[](int index) const;
|
|
|
|
int Count() const;
|
|
bool Exists(string key) const;
|
|
string KeyAt(int index) const;
|
|
CJsonValue ValueAt(int index) const;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CLASE PRINCIPAL: CJson |
|
|
//+------------------------------------------------------------------+
|
|
class CJson
|
|
{
|
|
protected:
|
|
SJsonNode m_nodes[];
|
|
int m_node_count;
|
|
int m_node_cap;
|
|
|
|
int m_pos;
|
|
int m_length;
|
|
|
|
int m_error;
|
|
int m_error_pos;
|
|
|
|
int m_stack[];
|
|
int m_stack_size;
|
|
int m_stack_cap;
|
|
|
|
bool m_parsed;
|
|
|
|
public:
|
|
uchar Buffer[];
|
|
|
|
CJson();
|
|
~CJson();
|
|
|
|
bool Parse();
|
|
void Clear();
|
|
void Reset();
|
|
|
|
bool HasError() const;
|
|
int GetError() const;
|
|
string GetErrorMessage() const;
|
|
int GetErrorPosition() const;
|
|
int GetLine() const;
|
|
int GetColumn() const;
|
|
|
|
CJsonValue Root();
|
|
bool IsValid() const;
|
|
|
|
CJsonValue operator[](string key);
|
|
CJsonValue operator[](int index);
|
|
bool Exists(string key);
|
|
|
|
int NodeCount() const;
|
|
|
|
//--- Acceso a nodos para CJsonValue
|
|
int Node_GetParent(int idx) const;
|
|
int Node_GetChild(int idx) const;
|
|
int Node_GetNext(int idx) const;
|
|
int Node_GetKeyOff(int idx) const;
|
|
int Node_GetKeyLen(int idx) const;
|
|
int Node_GetValOff(int idx) const;
|
|
int Node_GetValLen(int idx) const;
|
|
double Node_GetNumVal(int idx) const;
|
|
uchar Node_GetType(int idx) const;
|
|
uchar Node_GetFlags(int idx) const;
|
|
ushort Node_GetChildCnt(int idx) const;
|
|
bool Node_HasEscapes(int idx) const;
|
|
|
|
string ExtractNodeString(int node_idx);
|
|
string ExtractJsonString(int offset, int length, bool has_escapes);
|
|
|
|
int FindChildByKey(int node_idx, string key);
|
|
int FindChildByIndex(int node_idx, int index);
|
|
string GetChildKey(int node_idx, int index);
|
|
|
|
protected:
|
|
int AddNode(int parent, uchar type);
|
|
void SkipWhitespace();
|
|
void SetError(int err);
|
|
|
|
bool ParseValue(int parent);
|
|
bool ParseObject(int parent);
|
|
bool ParseArray(int parent);
|
|
bool ParseString(int parent, bool is_key);
|
|
bool ParseNumber(int parent);
|
|
bool ParseLiteral(int parent, uchar first);
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| IMPLEMENTACION CJsonValue |
|
|
//+------------------------------------------------------------------+
|
|
|
|
CJsonValue::CJsonValue()
|
|
: m_doc(NULL),
|
|
m_node(-1)
|
|
{
|
|
}
|
|
|
|
CJsonValue::CJsonValue(CJson* doc, int node)
|
|
: m_doc(doc),
|
|
m_node(node)
|
|
{
|
|
}
|
|
|
|
CJsonValue::CJsonValue(const CJsonValue& other)
|
|
: m_doc(other.m_doc),
|
|
m_node(other.m_node)
|
|
{
|
|
}
|
|
|
|
void CJsonValue::operator=(const CJsonValue& other)
|
|
{
|
|
m_doc = other.m_doc;
|
|
m_node = other.m_node;
|
|
}
|
|
|
|
bool CJsonValue::IsValid() const
|
|
{
|
|
return(m_doc != NULL && m_node >= 0);
|
|
}
|
|
|
|
int CJsonValue::NodeIndex() const
|
|
{
|
|
return(m_node);
|
|
}
|
|
|
|
bool CJsonValue::IsNull() const
|
|
{
|
|
return(GetType() == JT_NULL);
|
|
}
|
|
|
|
bool CJsonValue::IsBool() const
|
|
{
|
|
return(GetType() == JT_BOOL);
|
|
}
|
|
|
|
bool CJsonValue::IsInt() const
|
|
{
|
|
return(GetType() == JT_INT);
|
|
}
|
|
|
|
bool CJsonValue::IsDouble() const
|
|
{
|
|
return(GetType() == JT_DOUBLE);
|
|
}
|
|
|
|
bool CJsonValue::IsNumber() const
|
|
{
|
|
int t = GetType();
|
|
return(t == JT_INT || t == JT_DOUBLE);
|
|
}
|
|
|
|
bool CJsonValue::IsString() const
|
|
{
|
|
return(GetType() == JT_STRING);
|
|
}
|
|
|
|
bool CJsonValue::IsArray() const
|
|
{
|
|
return(GetType() == JT_ARRAY);
|
|
}
|
|
|
|
bool CJsonValue::IsObject() const
|
|
{
|
|
return(GetType() == JT_OBJECT);
|
|
}
|
|
|
|
int CJsonValue::GetType() const
|
|
{
|
|
if(!IsValid()) return(-1);
|
|
return((int)m_doc.Node_GetType(m_node));
|
|
}
|
|
|
|
string CJsonValue::ToString() const
|
|
{
|
|
if(m_doc == NULL) return("");
|
|
return(m_doc.ExtractNodeString(m_node));
|
|
}
|
|
|
|
int CJsonValue::ToInt() const
|
|
{
|
|
return((int)ToDouble());
|
|
}
|
|
|
|
double CJsonValue::ToDouble() const
|
|
{
|
|
if(!IsValid()) return(0.0);
|
|
|
|
int t = m_doc.Node_GetType(m_node);
|
|
if(t == JT_INT || t == JT_DOUBLE)
|
|
return(m_doc.Node_GetNumVal(m_node));
|
|
if(t == JT_BOOL)
|
|
return(m_doc.Node_GetNumVal(m_node) != 0.0 ? 1.0 : 0.0);
|
|
if(t == JT_STRING)
|
|
{
|
|
string s = m_doc.ExtractNodeString(m_node);
|
|
return(StringToDouble(s));
|
|
}
|
|
return(0.0);
|
|
}
|
|
|
|
bool CJsonValue::ToBool() const
|
|
{
|
|
if(!IsValid()) return(false);
|
|
|
|
int t = m_doc.Node_GetType(m_node);
|
|
if(t == JT_BOOL)
|
|
return(m_doc.Node_GetNumVal(m_node) != 0.0);
|
|
if(t == JT_INT || t == JT_DOUBLE)
|
|
return(m_doc.Node_GetNumVal(m_node) != 0.0);
|
|
if(t == JT_STRING)
|
|
{
|
|
string s = m_doc.ExtractNodeString(m_node);
|
|
return(s == "true" ? true : false);
|
|
}
|
|
return(false);
|
|
}
|
|
|
|
CJsonValue CJsonValue::operator[](string key) const
|
|
{
|
|
if(!IsValid()) return(CJsonValue(NULL, -1));
|
|
int child = m_doc.FindChildByKey(m_node, key);
|
|
if(child < 0) return(CJsonValue(NULL, -1));
|
|
return(CJsonValue(m_doc, child));
|
|
}
|
|
|
|
CJsonValue CJsonValue::operator[](int index) const
|
|
{
|
|
if(!IsValid()) return(CJsonValue(NULL, -1));
|
|
int child = m_doc.FindChildByIndex(m_node, index);
|
|
if(child < 0) return(CJsonValue(NULL, -1));
|
|
return(CJsonValue(m_doc, child));
|
|
}
|
|
|
|
int CJsonValue::Count() const
|
|
{
|
|
if(!IsValid()) return(0);
|
|
return((int)m_doc.Node_GetChildCnt(m_node));
|
|
}
|
|
|
|
bool CJsonValue::Exists(string key) const
|
|
{
|
|
if(!IsValid()) return(false);
|
|
return(m_doc.FindChildByKey(m_node, key) >= 0);
|
|
}
|
|
|
|
string CJsonValue::KeyAt(int index) const
|
|
{
|
|
if(!IsValid()) return("");
|
|
return(m_doc.GetChildKey(m_node, index));
|
|
}
|
|
|
|
CJsonValue CJsonValue::ValueAt(int index) const
|
|
{
|
|
if(!IsValid()) return(CJsonValue(NULL, -1));
|
|
int child = m_doc.FindChildByIndex(m_node, index);
|
|
if(child < 0) return(CJsonValue(NULL, -1));
|
|
return(CJsonValue(m_doc, child));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| IMPLEMENTACION CJson |
|
|
//+------------------------------------------------------------------+
|
|
|
|
CJson::CJson()
|
|
: m_node_count(0),
|
|
m_node_cap(0),
|
|
m_pos(0),
|
|
m_length(0),
|
|
m_error(0),
|
|
m_error_pos(-1),
|
|
m_stack_size(0),
|
|
m_stack_cap(0),
|
|
m_parsed(false)
|
|
{
|
|
Json_InitCharClass();
|
|
}
|
|
|
|
CJson::~CJson()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| NODE ACCESS METHODS |
|
|
//+------------------------------------------------------------------+
|
|
|
|
int CJson::Node_GetParent(int idx) const
|
|
{
|
|
if(idx < 0 || idx >= m_node_count) return(-1);
|
|
return(m_nodes[idx].parent);
|
|
}
|
|
|
|
int CJson::Node_GetChild(int idx) const
|
|
{
|
|
if(idx < 0 || idx >= m_node_count) return(-1);
|
|
return(m_nodes[idx].child);
|
|
}
|
|
|
|
int CJson::Node_GetNext(int idx) const
|
|
{
|
|
if(idx < 0 || idx >= m_node_count) return(-1);
|
|
return(m_nodes[idx].next);
|
|
}
|
|
|
|
int CJson::Node_GetKeyOff(int idx) const
|
|
{
|
|
if(idx < 0 || idx >= m_node_count) return(-1);
|
|
return(m_nodes[idx].key_off);
|
|
}
|
|
|
|
int CJson::Node_GetKeyLen(int idx) const
|
|
{
|
|
if(idx < 0 || idx >= m_node_count) return(0);
|
|
return(m_nodes[idx].key_len);
|
|
}
|
|
|
|
int CJson::Node_GetValOff(int idx) const
|
|
{
|
|
if(idx < 0 || idx >= m_node_count) return(-1);
|
|
return(m_nodes[idx].val_off);
|
|
}
|
|
|
|
int CJson::Node_GetValLen(int idx) const
|
|
{
|
|
if(idx < 0 || idx >= m_node_count) return(0);
|
|
return(m_nodes[idx].val_len);
|
|
}
|
|
|
|
double CJson::Node_GetNumVal(int idx) const
|
|
{
|
|
if(idx < 0 || idx >= m_node_count) return(0.0);
|
|
return(m_nodes[idx].num_val);
|
|
}
|
|
|
|
uchar CJson::Node_GetType(int idx) const
|
|
{
|
|
if(idx < 0 || idx >= m_node_count) return(255);
|
|
return(m_nodes[idx].type);
|
|
}
|
|
|
|
uchar CJson::Node_GetFlags(int idx) const
|
|
{
|
|
if(idx < 0 || idx >= m_node_count) return(0);
|
|
return(m_nodes[idx].flags);
|
|
}
|
|
|
|
ushort CJson::Node_GetChildCnt(int idx) const
|
|
{
|
|
if(idx < 0 || idx >= m_node_count) return(0);
|
|
return(m_nodes[idx].child_cnt);
|
|
}
|
|
|
|
bool CJson::Node_HasEscapes(int idx) const
|
|
{
|
|
if(idx < 0 || idx >= m_node_count) return(false);
|
|
return((m_nodes[idx].flags & 1) != 0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| LIFE CYCLE |
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CJson::Parse()
|
|
{
|
|
m_error = JERR_OK;
|
|
m_error_pos = -1;
|
|
m_pos = 0;
|
|
m_stack_size = 0;
|
|
m_parsed = false;
|
|
|
|
if(ArraySize(Buffer) == 0)
|
|
{
|
|
m_error = JERR_BUFFER_EMPTY;
|
|
m_error_pos = 0;
|
|
return(false);
|
|
}
|
|
|
|
m_length = ArraySize(Buffer);
|
|
|
|
if(m_node_cap == 0)
|
|
{
|
|
ArrayResize(m_nodes, JSON_INIT_NODES);
|
|
m_node_cap = JSON_INIT_NODES;
|
|
}
|
|
m_node_count = 0;
|
|
|
|
if(m_stack_cap < JSON_MAX_DEPTH)
|
|
{
|
|
ArrayResize(m_stack, JSON_MAX_DEPTH);
|
|
m_stack_cap = JSON_MAX_DEPTH;
|
|
}
|
|
m_stack_size = 0;
|
|
|
|
// Saltar BOM UTF-8 si esta presente
|
|
if(m_length >= 3 && Buffer[0] == 0xEF && Buffer[1] == 0xBB && Buffer[2] == 0xBF)
|
|
m_pos = 3;
|
|
|
|
SkipWhitespace();
|
|
|
|
if(m_pos >= m_length)
|
|
{
|
|
m_error = JERR_UNEXPECTED_END;
|
|
m_error_pos = m_pos;
|
|
return(false);
|
|
}
|
|
|
|
int root = AddNode(-1, JT_NULL);
|
|
if(root < 0) return(false);
|
|
|
|
if(!ParseValue(root))
|
|
return(false);
|
|
|
|
SkipWhitespace();
|
|
if(m_pos < m_length)
|
|
{
|
|
m_error = JERR_TRAILING_DATA;
|
|
m_error_pos = m_pos;
|
|
return(false);
|
|
}
|
|
|
|
m_parsed = true;
|
|
return(true);
|
|
}
|
|
|
|
void CJson::Clear()
|
|
{
|
|
m_node_count = 0;
|
|
m_pos = 0;
|
|
m_length = 0;
|
|
m_error = JERR_OK;
|
|
m_error_pos = -1;
|
|
m_stack_size = 0;
|
|
m_parsed = false;
|
|
}
|
|
|
|
void CJson::Reset()
|
|
{
|
|
Clear();
|
|
ArrayFree(m_nodes);
|
|
ArrayFree(m_stack);
|
|
m_node_cap = 0;
|
|
m_stack_cap = 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| ERROR HANDLING |
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CJson::HasError() const
|
|
{
|
|
return(m_error != JERR_OK);
|
|
}
|
|
|
|
int CJson::GetError() const
|
|
{
|
|
return(m_error);
|
|
}
|
|
|
|
string CJson::GetErrorMessage() const
|
|
{
|
|
switch(m_error)
|
|
{
|
|
case JERR_OK: return("OK");
|
|
case JERR_UNEXPECTED_CHAR: return("Caracter inesperado en la entrada");
|
|
case JERR_UNEXPECTED_END: return("Fin inesperado del buffer");
|
|
case JERR_INVALID_NUMBER: return("Formato de numero invalido");
|
|
case JERR_INVALID_STRING: return("Formato de string invalido");
|
|
case JERR_INVALID_ESCAPE: return("Secuencia de escape invalida");
|
|
case JERR_INVALID_UNICODE: return("Secuencia \\uXXXX invalida");
|
|
case JERR_EXPECTED_COLON: return("Se esperaba ':'");
|
|
case JERR_EXPECTED_COMMA: return("Se esperaba ','");
|
|
case JERR_EXPECTED_KEY: return("Se esperaba clave string en objeto");
|
|
case JERR_EXPECTED_VALUE: return("Se esperaba un valor");
|
|
case JERR_EXPECTED_OBJ_END: return("Se esperaba '}'");
|
|
case JERR_EXPECTED_ARR_END: return("Se esperaba ']'");
|
|
case JERR_DEPTH_EXCEEDED: return("Profundidad maxima excedida");
|
|
case JERR_NODE_LIMIT: return("Limite de nodos alcanzado");
|
|
case JERR_TRAILING_DATA: return("Datos extra despues del valor raiz");
|
|
case JERR_NOT_PARSED: return("No se ha llamado a Parse()");
|
|
case JERR_INDEX_OOB: return("Indice fuera de rango");
|
|
case JERR_KEY_NOT_FOUND: return("Clave no encontrada");
|
|
case JERR_TYPE_MISMATCH: return("Tipo de nodo incorrecto para la operacion");
|
|
case JERR_BUFFER_EMPTY: return("Buffer vacio o nulo");
|
|
}
|
|
return("Error desconocido");
|
|
}
|
|
|
|
int CJson::GetErrorPosition() const
|
|
{
|
|
return(m_error_pos);
|
|
}
|
|
|
|
int CJson::GetLine() const
|
|
{
|
|
if(m_error_pos < 0) return(1);
|
|
int line = 1;
|
|
for(int i = 0; i < m_error_pos && i < m_length; i++)
|
|
if(Buffer[i] == 0x0A) line++;
|
|
return(line);
|
|
}
|
|
|
|
int CJson::GetColumn() const
|
|
{
|
|
if(m_error_pos < 0) return(1);
|
|
int col = 1;
|
|
for(int i = m_error_pos - 1; i >= 0 && i < m_length; i--)
|
|
{
|
|
if(Buffer[i] == 0x0A) break;
|
|
col++;
|
|
}
|
|
return(col);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| ROOT ACCESS |
|
|
//+------------------------------------------------------------------+
|
|
|
|
CJsonValue CJson::Root()
|
|
{
|
|
if(!m_parsed || m_node_count == 0)
|
|
return(CJsonValue(NULL, -1));
|
|
return(CJsonValue(GetPointer(this), 0));
|
|
}
|
|
|
|
bool CJson::IsValid() const
|
|
{
|
|
return(m_parsed && m_node_count > 0);
|
|
}
|
|
|
|
CJsonValue CJson::operator[](string key)
|
|
{
|
|
return(Root()[key]);
|
|
}
|
|
|
|
CJsonValue CJson::operator[](int index)
|
|
{
|
|
return(Root()[index]);
|
|
}
|
|
|
|
bool CJson::Exists(string key)
|
|
{
|
|
return(Root().Exists(key));
|
|
}
|
|
|
|
int CJson::NodeCount() const
|
|
{
|
|
return(m_node_count);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| METODOS INTERNOS DE PARSEO |
|
|
//+------------------------------------------------------------------+
|
|
|
|
int CJson::AddNode(int parent, uchar type)
|
|
{
|
|
if(m_node_count >= m_node_cap)
|
|
{
|
|
int new_cap = m_node_cap * JSON_GROW_FACTOR;
|
|
if(new_cap < JSON_INIT_NODES) new_cap = JSON_INIT_NODES;
|
|
if(ArrayResize(m_nodes, new_cap) != new_cap)
|
|
{
|
|
m_error = JERR_NODE_LIMIT;
|
|
m_error_pos = m_pos;
|
|
return(-1);
|
|
}
|
|
m_node_cap = new_cap;
|
|
}
|
|
|
|
int idx = m_node_count;
|
|
m_node_count++;
|
|
|
|
m_nodes[idx].parent = parent;
|
|
m_nodes[idx].child = -1;
|
|
m_nodes[idx].next = -1;
|
|
m_nodes[idx].key_off = -1;
|
|
m_nodes[idx].key_len = 0;
|
|
m_nodes[idx].val_off = -1;
|
|
m_nodes[idx].val_len = 0;
|
|
m_nodes[idx].num_val = 0.0;
|
|
m_nodes[idx].type = type;
|
|
m_nodes[idx].flags = 0;
|
|
m_nodes[idx].child_cnt = 0;
|
|
|
|
// Enlazar con el padre
|
|
if(parent >= 0 && parent < m_node_count)
|
|
{
|
|
if(m_nodes[parent].child < 0)
|
|
m_nodes[parent].child = idx;
|
|
else
|
|
{
|
|
int last = m_nodes[parent].child;
|
|
while(m_nodes[last].next >= 0)
|
|
last = m_nodes[last].next;
|
|
m_nodes[last].next = idx;
|
|
}
|
|
m_nodes[parent].child_cnt++;
|
|
}
|
|
|
|
return(idx);
|
|
}
|
|
|
|
void CJson::SkipWhitespace()
|
|
{
|
|
while(m_pos < m_length)
|
|
{
|
|
if(Buffer[m_pos] > 32) // CAMBIADO antes usaba la tabla..
|
|
return;
|
|
m_pos++;
|
|
}
|
|
}
|
|
|
|
void CJson::SetError(int err)
|
|
{
|
|
m_error = err;
|
|
m_error_pos = m_pos;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| PARSEADOR PRINCIPAL |
|
|
//+------------------------------------------------------------------+
|
|
|
|
bool CJson::ParseValue(int parent)
|
|
{
|
|
SkipWhitespace();
|
|
if(m_pos >= m_length)
|
|
{
|
|
SetError(JERR_UNEXPECTED_END);
|
|
return(false);
|
|
}
|
|
|
|
uchar c = Buffer[m_pos];
|
|
uchar cls = s_char_class[c];
|
|
|
|
switch(cls)
|
|
{
|
|
case _JC_STRUCT:
|
|
{
|
|
if(c == '{') return(ParseObject(parent));
|
|
if(c == '[') return(ParseArray(parent));
|
|
SetError(JERR_UNEXPECTED_CHAR);
|
|
return(false);
|
|
}
|
|
case _JC_QUOTE:
|
|
return(ParseString(parent, false));
|
|
|
|
case _JC_DIGIT:
|
|
case _JC_MINUS:
|
|
return(ParseNumber(parent));
|
|
|
|
case _JC_LIT:
|
|
return(ParseLiteral(parent, c));
|
|
|
|
default:
|
|
SetError(JERR_UNEXPECTED_CHAR);
|
|
return(false);
|
|
}
|
|
}
|
|
|
|
bool CJson::ParseObject(int parent)
|
|
{
|
|
m_pos++;
|
|
|
|
int obj_node = AddNode(parent, (uchar)JT_OBJECT);
|
|
if(obj_node < 0) return(false);
|
|
|
|
SkipWhitespace();
|
|
if(m_pos < m_length && Buffer[m_pos] == '}')
|
|
{
|
|
m_pos++;
|
|
return(true);
|
|
}
|
|
|
|
while(true)
|
|
{
|
|
SkipWhitespace();
|
|
if(m_pos >= m_length)
|
|
{
|
|
SetError(JERR_UNEXPECTED_END);
|
|
return(false);
|
|
}
|
|
|
|
if(Buffer[m_pos] != '"')
|
|
{
|
|
if(Buffer[m_pos] == '}')
|
|
{
|
|
m_pos++;
|
|
return(true);
|
|
}
|
|
SetError(JERR_EXPECTED_KEY);
|
|
return(false);
|
|
}
|
|
|
|
if(!ParseString(obj_node, true))
|
|
return(false);
|
|
|
|
SkipWhitespace();
|
|
if(m_pos >= m_length)
|
|
{
|
|
SetError(JERR_UNEXPECTED_END);
|
|
return(false);
|
|
}
|
|
if(Buffer[m_pos] != ':')
|
|
{
|
|
SetError(JERR_EXPECTED_COLON);
|
|
return(false);
|
|
}
|
|
m_pos++;
|
|
|
|
SkipWhitespace();
|
|
if(!ParseValue(obj_node))
|
|
return(false);
|
|
|
|
SkipWhitespace();
|
|
if(m_pos >= m_length)
|
|
{
|
|
SetError(JERR_UNEXPECTED_END);
|
|
return(false);
|
|
}
|
|
|
|
if(Buffer[m_pos] == ',')
|
|
{
|
|
m_pos++;
|
|
continue;
|
|
}
|
|
else if(Buffer[m_pos] == '}')
|
|
{
|
|
m_pos++;
|
|
return(true);
|
|
}
|
|
else
|
|
{
|
|
SetError(JERR_EXPECTED_COMMA);
|
|
return(false);
|
|
}
|
|
}
|
|
|
|
SetError(JERR_UNEXPECTED_END);
|
|
return(false);
|
|
}
|
|
|
|
bool CJson::ParseArray(int parent)
|
|
{
|
|
m_pos++;
|
|
|
|
int arr_node = AddNode(parent, (uchar)JT_ARRAY);
|
|
if(arr_node < 0) return(false);
|
|
|
|
SkipWhitespace();
|
|
if(m_pos < m_length && Buffer[m_pos] == ']')
|
|
{
|
|
m_pos++;
|
|
return(true);
|
|
}
|
|
|
|
while(true)
|
|
{
|
|
SkipWhitespace();
|
|
if(!ParseValue(arr_node))
|
|
return(false);
|
|
|
|
SkipWhitespace();
|
|
if(m_pos >= m_length)
|
|
{
|
|
SetError(JERR_UNEXPECTED_END);
|
|
return(false);
|
|
}
|
|
|
|
if(Buffer[m_pos] == ',')
|
|
{
|
|
m_pos++;
|
|
continue;
|
|
}
|
|
else if(Buffer[m_pos] == ']')
|
|
{
|
|
m_pos++;
|
|
return(true);
|
|
}
|
|
else
|
|
{
|
|
SetError(JERR_EXPECTED_COMMA);
|
|
return(false);
|
|
}
|
|
}
|
|
|
|
SetError(JERR_UNEXPECTED_END);
|
|
return(false);
|
|
}
|
|
|
|
bool CJson::ParseString(int parent, bool is_key)
|
|
{
|
|
m_pos++; // consumir '"'
|
|
|
|
int content_start = m_pos;
|
|
bool has_escapes = false;
|
|
|
|
while(m_pos < m_length)
|
|
{
|
|
uchar c = Buffer[m_pos];
|
|
|
|
if(c == '"')
|
|
{
|
|
int content_len = m_pos - content_start;
|
|
|
|
int str_node = AddNode(parent, (uchar)JT_STRING);
|
|
if(str_node < 0) return(false);
|
|
|
|
if(is_key)
|
|
{
|
|
m_nodes[str_node].key_off = content_start;
|
|
m_nodes[str_node].key_len = content_len;
|
|
}
|
|
else
|
|
{
|
|
m_nodes[str_node].val_off = content_start;
|
|
m_nodes[str_node].val_len = content_len;
|
|
}
|
|
|
|
if(has_escapes)
|
|
m_nodes[str_node].flags |= 1;
|
|
|
|
m_pos++;
|
|
return(true);
|
|
}
|
|
|
|
if(c == '\\')
|
|
{
|
|
has_escapes = true;
|
|
m_pos+=2;
|
|
continue;
|
|
}
|
|
/* else if(c < 0x20) CODIGO CORREGIDO DADO QUE DABA ERROR:
|
|
2026.06.28 08:13:50.783 BenMqlLite (EURUSD,MN1) Secuencia de escape invalida
|
|
2026.06.28 08:13:50.783 BenMqlLite (EURUSD,MN1) 6031 | 28
|
|
{
|
|
SetError(JERR_INVALID_STRING);
|
|
return(false);
|
|
}
|
|
else*/
|
|
m_pos++;
|
|
}
|
|
|
|
SetError(JERR_UNEXPECTED_END);
|
|
return(false);
|
|
}
|
|
|
|
bool CJson::ParseNumber(int parent)
|
|
{
|
|
int start = m_pos;
|
|
bool is_float = false;
|
|
|
|
if(Buffer[m_pos] == '-')
|
|
m_pos++;
|
|
|
|
if(m_pos >= m_length)
|
|
{
|
|
SetError(JERR_UNEXPECTED_END);
|
|
return(false);
|
|
}
|
|
|
|
if(Buffer[m_pos] == '0')
|
|
m_pos++;
|
|
else if(Buffer[m_pos] >= '1' && Buffer[m_pos] <= '9')
|
|
{
|
|
m_pos++;
|
|
while(m_pos < m_length && Buffer[m_pos] >= '0' && Buffer[m_pos] <= '9')
|
|
m_pos++;
|
|
}
|
|
else
|
|
{
|
|
SetError(JERR_INVALID_NUMBER);
|
|
return(false);
|
|
}
|
|
|
|
if(m_pos < m_length && Buffer[m_pos] == '.')
|
|
{
|
|
is_float = true;
|
|
m_pos++;
|
|
if(m_pos >= m_length || Buffer[m_pos] < '0' || Buffer[m_pos] > '9')
|
|
{
|
|
SetError(JERR_INVALID_NUMBER);
|
|
return(false);
|
|
}
|
|
while(m_pos < m_length && Buffer[m_pos] >= '0' && Buffer[m_pos] <= '9')
|
|
m_pos++;
|
|
}
|
|
|
|
if(m_pos < m_length && (Buffer[m_pos] == 'e' || Buffer[m_pos] == 'E'))
|
|
{
|
|
is_float = true;
|
|
m_pos++;
|
|
if(m_pos < m_length && (Buffer[m_pos] == '+' || Buffer[m_pos] == '-'))
|
|
m_pos++;
|
|
if(m_pos >= m_length || Buffer[m_pos] < '0' || Buffer[m_pos] > '9')
|
|
{
|
|
SetError(JERR_INVALID_NUMBER);
|
|
return(false);
|
|
}
|
|
while(m_pos < m_length && Buffer[m_pos] >= '0' && Buffer[m_pos] <= '9')
|
|
m_pos++;
|
|
}
|
|
|
|
string num_str = CharArrayToString(Buffer, start, m_pos - start);
|
|
double value = StringToDouble(num_str);
|
|
|
|
uchar node_type = is_float ? (uchar)JT_DOUBLE : (uchar)JT_INT;
|
|
int num_node = AddNode(parent, node_type);
|
|
if(num_node < 0) return(false);
|
|
|
|
m_nodes[num_node].num_val = value;
|
|
m_nodes[num_node].val_off = start;
|
|
m_nodes[num_node].val_len = m_pos - start;
|
|
|
|
return(true);
|
|
}
|
|
|
|
bool CJson::ParseLiteral(int parent, uchar first)
|
|
{
|
|
if(first == 't')
|
|
{
|
|
if(m_pos + 4 > m_length) { SetError(JERR_UNEXPECTED_END); return(false); }
|
|
if(Buffer[m_pos+1] != 'r' || Buffer[m_pos+2] != 'u' || Buffer[m_pos+3] != 'e')
|
|
{ SetError(JERR_UNEXPECTED_CHAR); return(false); }
|
|
|
|
int node = AddNode(parent, (uchar)JT_BOOL);
|
|
if(node < 0) return(false);
|
|
m_nodes[node].num_val = 1.0;
|
|
m_nodes[node].val_off = m_pos;
|
|
m_nodes[node].val_len = 4;
|
|
m_pos += 4;
|
|
}
|
|
else if(first == 'f')
|
|
{
|
|
if(m_pos + 5 > m_length) { SetError(JERR_UNEXPECTED_END); return(false); }
|
|
if(Buffer[m_pos+1] != 'a' || Buffer[m_pos+2] != 'l' || Buffer[m_pos+3] != 's' || Buffer[m_pos+4] != 'e')
|
|
{ SetError(JERR_UNEXPECTED_CHAR); return(false); }
|
|
|
|
int node = AddNode(parent, (uchar)JT_BOOL);
|
|
if(node < 0) return(false);
|
|
m_nodes[node].num_val = 0.0;
|
|
m_nodes[node].val_off = m_pos;
|
|
m_nodes[node].val_len = 5;
|
|
m_pos += 5;
|
|
}
|
|
else if(first == 'n')
|
|
{
|
|
if(m_pos + 4 > m_length) { SetError(JERR_UNEXPECTED_END); return(false); }
|
|
if(Buffer[m_pos+1] != 'u' || Buffer[m_pos+2] != 'l' || Buffer[m_pos+3] != 'l')
|
|
{ SetError(JERR_UNEXPECTED_CHAR); return(false); }
|
|
|
|
int node = AddNode(parent, (uchar)JT_NULL);
|
|
if(node < 0) return(false);
|
|
m_nodes[node].val_off = m_pos;
|
|
m_nodes[node].val_len = 4;
|
|
m_pos += 4;
|
|
}
|
|
else
|
|
{
|
|
SetError(JERR_UNEXPECTED_CHAR);
|
|
return(false);
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| EXTRACCION DE VALORES |
|
|
//+------------------------------------------------------------------+
|
|
|
|
string CJson::ExtractNodeString(int node_idx)
|
|
{
|
|
if(node_idx < 0 || node_idx >= m_node_count)
|
|
return("");
|
|
|
|
uchar t = m_nodes[node_idx].type;
|
|
int off = m_nodes[node_idx].val_off;
|
|
int len = m_nodes[node_idx].val_len;
|
|
|
|
if(t == JT_STRING)
|
|
{
|
|
if(off < 0 || len <= 0)
|
|
return("");
|
|
|
|
bool has_esc = (m_nodes[node_idx].flags & 1) != 0;
|
|
return(ExtractJsonString(off, len, has_esc));
|
|
}
|
|
else if(t == JT_INT || t == JT_DOUBLE)
|
|
{
|
|
if(off >= 0 && len > 0)
|
|
return(CharArrayToString(Buffer, off, len));
|
|
return(DoubleToString(m_nodes[node_idx].num_val, 8));
|
|
}
|
|
else if(t == JT_BOOL)
|
|
return(m_nodes[node_idx].num_val != 0.0 ? "true" : "false");
|
|
else if(t == JT_NULL)
|
|
return("null");
|
|
else if(t == JT_OBJECT)
|
|
return("[object]");
|
|
else if(t == JT_ARRAY)
|
|
return("[array]");
|
|
|
|
return("");
|
|
}
|
|
|
|
string CJson::ExtractJsonString(int offset, int length, bool has_escapes)
|
|
{
|
|
if(length <= 0) return("");
|
|
|
|
if(!has_escapes)
|
|
return(CharArrayToString(Buffer, offset, length));
|
|
|
|
uchar result[];
|
|
ArrayResize(result, length + 8);
|
|
int ri = 0;
|
|
int i = 0;
|
|
|
|
while(i < length)
|
|
{
|
|
uchar c = Buffer[offset + i];
|
|
|
|
if(c == '\\')
|
|
{
|
|
i++;
|
|
if(i >= length) break;
|
|
|
|
uchar esc = Buffer[offset + i];
|
|
switch(esc)
|
|
{
|
|
case '"': result[ri++] = 0x22; break;
|
|
case '\\': result[ri++] = 0x5C; break;
|
|
case '/': result[ri++] = 0x2F; break;
|
|
case 'b': result[ri++] = 0x08; break;
|
|
case 'f': result[ri++] = 0x0C; break;
|
|
case 'n': result[ri++] = 0x0A; break;
|
|
case 'r': result[ri++] = 0x0D; break;
|
|
case 't': result[ri++] = 0x09; break;
|
|
|
|
case 'u':
|
|
{
|
|
i++;
|
|
int hex_val = 0;
|
|
for(int j = 0; j < 4; j++)
|
|
{
|
|
uchar h = Buffer[offset + i];
|
|
hex_val = (hex_val << 4);
|
|
if(h >= '0' && h <= '9')
|
|
hex_val += (h - '0');
|
|
else if(h >= 'A' && h <= 'F')
|
|
hex_val += (h - 'A' + 10);
|
|
else if(h >= 'a' && h <= 'f')
|
|
hex_val += (h - 'a' + 10);
|
|
i++;
|
|
}
|
|
i--;
|
|
|
|
if(hex_val < 0x80)
|
|
result[ri++] = (uchar)hex_val;
|
|
else if(hex_val < 0x800)
|
|
{
|
|
result[ri++] = (uchar)(0xC0 | (hex_val >> 6));
|
|
result[ri++] = (uchar)(0x80 | (hex_val & 0x3F));
|
|
}
|
|
else
|
|
{
|
|
result[ri++] = (uchar)(0xE0 | (hex_val >> 12));
|
|
result[ri++] = (uchar)(0x80 | ((hex_val >> 6) & 0x3F));
|
|
result[ri++] = (uchar)(0x80 | (hex_val & 0x3F));
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
result[ri++] = esc;
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
result[ri++] = c;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if(ri == 0) return("");
|
|
return(CharArrayToString(result, 0, ri));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| NAVEGACION |
|
|
//+------------------------------------------------------------------+
|
|
|
|
int CJson::FindChildByKey(int node_idx, string key)
|
|
{
|
|
if(node_idx < 0 || node_idx >= m_node_count) return(-1);
|
|
if(m_nodes[node_idx].type != JT_OBJECT) return(-1);
|
|
|
|
int child = m_nodes[node_idx].child;
|
|
while(child >= 0)
|
|
{
|
|
int koff = m_nodes[child].key_off;
|
|
int klen = m_nodes[child].key_len;
|
|
bool hes = (m_nodes[child].flags & 1) != 0;
|
|
|
|
if(koff >= 0 && klen > 0)
|
|
{
|
|
string child_key = ExtractJsonString(koff, klen, hes);
|
|
if(child_key == key)
|
|
return(child);
|
|
}
|
|
child = m_nodes[child].next;
|
|
}
|
|
|
|
return(-1);
|
|
}
|
|
|
|
int CJson::FindChildByIndex(int node_idx, int index)
|
|
{
|
|
if(node_idx < 0 || node_idx >= m_node_count) return(-1);
|
|
|
|
uchar t = m_nodes[node_idx].type;
|
|
if(t != JT_ARRAY && t != JT_OBJECT) return(-1);
|
|
if(index < 0 || (uint)index >= (uint)m_nodes[node_idx].child_cnt) return(-1);
|
|
|
|
int child = m_nodes[node_idx].child;
|
|
for(int i = 0; i < index && child >= 0; i++)
|
|
child = m_nodes[child].next;
|
|
|
|
return(child);
|
|
}
|
|
|
|
string CJson::GetChildKey(int node_idx, int index)
|
|
{
|
|
int child = FindChildByIndex(node_idx, index);
|
|
if(child < 0) return("");
|
|
|
|
int koff = m_nodes[child].key_off;
|
|
int klen = m_nodes[child].key_len;
|
|
bool hes = (m_nodes[child].flags & 1) != 0;
|
|
|
|
if(koff < 0 || klen <= 0) return("");
|
|
|
|
return(ExtractJsonString(koff, klen, hes));
|
|
}
|
|
//+------------------------------------------------------------------+
|