You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1158 lines
36 KiB
C
1158 lines
36 KiB
C
/***************************************************************************
|
|
* Routines for dealing with libmseed extra headers stored as JSON.
|
|
*
|
|
* This file is part of the miniSEED Library.
|
|
*
|
|
* Copyright (c) 2023 Chad Trabant, EarthScope Data Services
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
***************************************************************************/
|
|
|
|
#include "libmseed.h"
|
|
#include "extraheaders.h"
|
|
|
|
/* Private allocation wrappers for yyjson's allocator definition */
|
|
void *_priv_malloc(void *ctx, size_t size) {
|
|
UNUSED(ctx);
|
|
return libmseed_memory.malloc(size);
|
|
}
|
|
|
|
void *_priv_realloc(void *ctx, void *ptr, size_t oldsize, size_t size) {
|
|
UNUSED(ctx);
|
|
UNUSED(oldsize);
|
|
return libmseed_memory.realloc(ptr, size);
|
|
}
|
|
|
|
void _priv_free(void *ctx, void *ptr) {
|
|
UNUSED(ctx);
|
|
libmseed_memory.free(ptr);
|
|
}
|
|
|
|
/***************************************************************************
|
|
* Internal routine to parse JSON strings into internal state storage.
|
|
*
|
|
* The state container is allocated if needed and if JSON is supplied,
|
|
* it is parsed into the immutable form.
|
|
*
|
|
* @param[in] jsonstring JSON string to parse
|
|
* @param[in] length Length of JSON string
|
|
* @param[in] parsed Internal parsed state
|
|
*
|
|
* @returns A LM_PARSED_JSON* on success or NULL on error
|
|
*
|
|
* \sa mseh_free_parsestate()
|
|
***************************************************************************/
|
|
static LM_PARSED_JSON*
|
|
parse_json (char *jsonstring, size_t length, LM_PARSED_JSON *parsed)
|
|
{
|
|
yyjson_read_flag flg = YYJSON_READ_NOFLAG;
|
|
yyjson_read_err err;
|
|
yyjson_alc alc = {_priv_malloc, _priv_realloc, _priv_free, NULL};
|
|
|
|
/* Allocate parsed state if needed */
|
|
if (!parsed)
|
|
{
|
|
if ((parsed = libmseed_memory.malloc (sizeof (LM_PARSED_JSON))) == NULL)
|
|
{
|
|
ms_log (2, "%s() Cannot allocate memory for internal JSON parsing state\n", __func__);
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
parsed->doc = NULL;
|
|
parsed->mut_doc = NULL;
|
|
}
|
|
}
|
|
|
|
/* Nothing to parse */
|
|
if (jsonstring == NULL || length == 0)
|
|
{
|
|
return parsed;
|
|
}
|
|
|
|
/* Free existing immutable doc */
|
|
if (parsed->doc)
|
|
{
|
|
yyjson_doc_free (parsed->doc);
|
|
parsed->doc = NULL;
|
|
}
|
|
|
|
/* Free existing mutable document */
|
|
if (parsed->mut_doc != NULL)
|
|
{
|
|
yyjson_mut_doc_free (parsed->mut_doc);
|
|
parsed->mut_doc = NULL;
|
|
}
|
|
|
|
/* Parse JSON into immutable form */
|
|
if ((parsed->doc = yyjson_read_opts (jsonstring, length, flg, &alc, &err)) == NULL)
|
|
{
|
|
ms_log (2, "%s() Cannot parse extra header JSON: %s\n",
|
|
__func__, (err.msg) ? err.msg : "Unknown error");
|
|
return NULL;
|
|
}
|
|
|
|
return parsed;
|
|
}
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Search for and return an extra header value.
|
|
*
|
|
* The extra header value is specified as a JSON Pointer (RFC 6901), e.g.
|
|
* \c '/objectA/objectB/header'.
|
|
*
|
|
* This routine can get used to test for the existence of a value
|
|
* without returning the value by setting \a value to NULL.
|
|
*
|
|
* If the target item is found (and \a value parameter is set) the
|
|
* value will be copied into the memory specified by \c value. The
|
|
* \a type value specifies the data type expected.
|
|
*
|
|
* If a \a parsestate pointer is supplied, the parsed (deserialized) JSON
|
|
* data are stored here. This value may be used in subsequent calls to
|
|
* avoid re-parsing the JSON. The data must be freed with
|
|
* mseh_free_parsestate() when done reading the JSON. If this value
|
|
* is NULL the parse state will be created and destroyed on each call.
|
|
*
|
|
* @param[in] msr Parsed miniSEED record to search
|
|
* @param[in] ptr Header value desired, as JSON Pointer
|
|
* @param[out] value Buffer for value, of type \c type
|
|
* @param[in] type Type of value expected, one of:
|
|
* @parblock
|
|
* - \c 'n' - \a value is type \a double
|
|
* - \c 'i' - \a value is type \a int64_t
|
|
* - \c 's' - \a value is type \a char* (maximum length is: \c maxlength - 1)
|
|
* - \c 'b' - \a value of type \a int (boolean value of 0 or 1)
|
|
* @endparblock
|
|
* @param[in] maxlength Maximum length of string value
|
|
* @param[in] parsestate Parsed state for multiple operations, can be NULL
|
|
*
|
|
* @retval 0 on success
|
|
* @retval 1 when the value was not found
|
|
* @retval 2 when the value is of a different type
|
|
* @returns A (negative) libmseed error code on error
|
|
*
|
|
* \ref MessageOnError - this function logs a message on error
|
|
*
|
|
* \sa mseh_free_parsestate()
|
|
***************************************************************************/
|
|
int
|
|
mseh_get_ptr_r (const MS3Record *msr, const char *ptr,
|
|
void *value, char type, size_t maxlength,
|
|
LM_PARSED_JSON **parsestate)
|
|
{
|
|
LM_PARSED_JSON *parsed = (parsestate) ? *parsestate : NULL;
|
|
|
|
yyjson_alc alc = {_priv_malloc, _priv_realloc, _priv_free, NULL};
|
|
yyjson_val *extravalue = NULL;
|
|
const char *stringvalue = NULL;
|
|
|
|
int retval = 0;
|
|
|
|
if (!msr || !ptr)
|
|
{
|
|
ms_log (2, "%s() Required argument not defined: 'msr' or 'ptr'\n", __func__);
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
/* Nothing can be found in no headers */
|
|
if (!msr->extralength)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if (!msr->extra)
|
|
{
|
|
ms_log (2, "%s() Expected extra headers (msr->extra) are not present\n", __func__);
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
/* Detect invalid JSON Pointer, i.e. with no root '/' designation */
|
|
if (ptr[0] != '/')
|
|
{
|
|
ms_log (2, "%s() Unsupported ptr notation: %s\n", __func__, ptr);
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
/* Parse JSON extra headers if not available in state */
|
|
if (parsed == NULL)
|
|
{
|
|
/* Parse to immutable state */
|
|
parsed = parse_json(msr->extra, msr->extralength, parsed);
|
|
|
|
if (parsed == NULL)
|
|
{
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
/* Set supplied state pointer */
|
|
if (parsestate != NULL)
|
|
{
|
|
*parsestate = parsed;
|
|
}
|
|
}
|
|
|
|
/* Create immutable document from mutable if needed */
|
|
if (parsed->mut_doc != NULL && parsed->doc == NULL)
|
|
{
|
|
if ((parsed->doc = yyjson_mut_doc_imut_copy (parsed->mut_doc, &alc)) == NULL)
|
|
{
|
|
ms_log (2, "%s() Cannot create immutable document from mutable\n", __func__);
|
|
|
|
if (parsestate == NULL)
|
|
{
|
|
mseh_free_parsestate (&parsed);
|
|
}
|
|
|
|
return MS_GENERROR;
|
|
}
|
|
}
|
|
|
|
/* Get target value */
|
|
extravalue = yyjson_doc_ptr_get(parsed->doc, ptr);
|
|
|
|
if (extravalue == NULL)
|
|
{
|
|
retval = 1;
|
|
}
|
|
else if (type == 'n' && yyjson_is_num (extravalue))
|
|
{
|
|
if (value)
|
|
*((double *)value) = unsafe_yyjson_get_num (extravalue);
|
|
}
|
|
else if (type == 'i' && yyjson_is_int (extravalue))
|
|
{
|
|
if (value)
|
|
*((int64_t *)value) = unsafe_yyjson_get_int (extravalue);
|
|
}
|
|
else if (type == 's' && yyjson_is_str (extravalue))
|
|
{
|
|
if (value)
|
|
{
|
|
stringvalue = unsafe_yyjson_get_str (extravalue);
|
|
strncpy ((char *)value, stringvalue, maxlength - 1);
|
|
((char *)value)[maxlength - 1] = '\0';
|
|
}
|
|
}
|
|
else if (type == 'b' && yyjson_is_bool(extravalue))
|
|
{
|
|
if (value)
|
|
*((int *)value) = (unsafe_yyjson_get_bool (extravalue)) ? 1 : 0;
|
|
}
|
|
/* Return wrong type indicator if a value was requested */
|
|
else if (value)
|
|
{
|
|
retval = 2;
|
|
}
|
|
|
|
/* Free parse state if not being retained */
|
|
if (parsestate == NULL)
|
|
{
|
|
mseh_free_parsestate (&parsed);
|
|
}
|
|
|
|
return retval;
|
|
} /* End of mseh_get_ptr_r() */
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Set the value of extra header values
|
|
*
|
|
* The extra header value is specified as a JSON Pointer (RFC 6901), e.g.
|
|
* \c '/objectA/objectB/header'.
|
|
*
|
|
* For most value types, if the \a ptr or final header values do not exist
|
|
* they will be created. If the header value exists it will be replaced.
|
|
* When the value type is 'M', for Merge Patch (RFC 7386), the location
|
|
* indicated by \a ptr must exist.
|
|
*
|
|
* The \a type value specifies the data type expected for \c value.
|
|
*
|
|
* If a \a parsestate pointer is supplied, the parsed (deserialized) JSON
|
|
* data are stored here. This value may be used in subsequent calls to
|
|
* avoid re-parsing the JSON. When done setting headers using this
|
|
* functionality the following \a must be done:
|
|
* 1. call mseh_serialize() to create the JSON headers before writing the record
|
|
* 2. free the \a parsestate data with mseh_free_parsestate()
|
|
* If this value is NULL the parse state will be created and destroyed
|
|
* on each call.
|
|
*
|
|
* @param[in] msr Parsed miniSEED record to modify
|
|
* @param[in] ptr Header value to set as JSON Pointer, or JSON Merge Patch
|
|
* @param[in] value Buffer for value, of type \c type
|
|
* @param[in] type Type of value expected, one of:
|
|
* @parblock
|
|
* - \c 'n' - \a value is type \a double
|
|
* - \c 'i' - \a value is type \a int64_t
|
|
* - \c 's' - \a value is type \a char*
|
|
* - \c 'b' - \a value is type \a int (boolean value of 0 or 1)
|
|
* - \c 'M' - \a value is type \a char* and a Merge Patch to apply at \a ptr
|
|
* - \c 'V' - \a value is type \a yyjson_mut_val* to _set/replace_ (internal use)
|
|
* - \c 'A' - \a value is type \a yyjson_mut_val* to _append to array_ (internal use)
|
|
* @endparblock
|
|
* @param[in] parsestate Parsed state for multiple operations, can be NULL
|
|
*
|
|
* @retval 0 on success, otherwise a (negative) libmseed error code.
|
|
*
|
|
* \ref MessageOnError - this function logs a message on error
|
|
*
|
|
* \sa mseh_free_parsestate()
|
|
* \sa mseh_serialize()
|
|
***************************************************************************/
|
|
int
|
|
mseh_set_ptr_r (MS3Record *msr, const char *ptr,
|
|
void *value, char type,
|
|
LM_PARSED_JSON **parsestate)
|
|
{
|
|
LM_PARSED_JSON *parsed = (parsestate) ? *parsestate : NULL;
|
|
yyjson_alc alc = {_priv_malloc, _priv_realloc, _priv_free, NULL};
|
|
yyjson_doc *patch_idoc = NULL;
|
|
yyjson_mut_doc *patch_doc = NULL;
|
|
yyjson_mut_val *merged_val = NULL;
|
|
yyjson_mut_val *target_val = NULL;
|
|
yyjson_mut_val *array_val = NULL;
|
|
bool rv = false;
|
|
|
|
if (!msr || !ptr || !value)
|
|
{
|
|
ms_log (2, "%s() Required argument not defined: 'msr', 'ptr', or 'value'\n", __func__);
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
/* Detect invalid JSON Pointer, i.e. with no root '/' designation */
|
|
if (ptr[0] != '/' && type != 'M')
|
|
{
|
|
ms_log (2, "%s() Unsupported JSON Pointer notation: %s\n", __func__, ptr);
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
/* Parse JSON extra headers if not available in state */
|
|
if (parsed == NULL)
|
|
{
|
|
/* Allocate state container and parse to immutable form */
|
|
parsed = parse_json (msr->extra, msr->extralength, parsed);
|
|
|
|
if (parsed == NULL)
|
|
{
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
/* Set supplied state pointer */
|
|
if (parsestate != NULL)
|
|
{
|
|
*parsestate = parsed;
|
|
}
|
|
}
|
|
|
|
/* Generate mutable document from immutable form if needed */
|
|
if (parsed->doc != NULL && parsed->mut_doc == NULL)
|
|
{
|
|
if ((parsed->mut_doc = yyjson_doc_mut_copy (parsed->doc, &alc)) == NULL)
|
|
{
|
|
ms_log (2, "%s() Cannot create mutable JSON document\n", __func__);
|
|
|
|
if (parsestate == NULL)
|
|
{
|
|
mseh_free_parsestate (&parsed);
|
|
}
|
|
|
|
return MS_GENERROR;
|
|
}
|
|
}
|
|
|
|
/* Initialize empty mutable document if needed */
|
|
if (parsed->mut_doc == NULL)
|
|
{
|
|
if ((parsed->mut_doc = yyjson_mut_doc_new (&alc)) == NULL)
|
|
{
|
|
ms_log (2, "%s() Cannot initialize mutable JSON document\n", __func__);
|
|
|
|
if (parsestate == NULL)
|
|
{
|
|
mseh_free_parsestate (&parsed);
|
|
}
|
|
|
|
return MS_GENERROR;
|
|
}
|
|
}
|
|
|
|
/* Set (or replace) header value at ptr */
|
|
switch (type)
|
|
{
|
|
case 'n':
|
|
rv = yyjson_mut_doc_ptr_set (parsed->mut_doc, ptr,
|
|
yyjson_mut_real (parsed->mut_doc, *((double *)value)));
|
|
break;
|
|
case 'i':
|
|
rv = yyjson_mut_doc_ptr_set (parsed->mut_doc, ptr,
|
|
yyjson_mut_sint (parsed->mut_doc, *((int64_t *)value)));
|
|
break;
|
|
case 's':
|
|
rv = yyjson_mut_doc_ptr_set (parsed->mut_doc, ptr,
|
|
yyjson_mut_strcpy (parsed->mut_doc, (const char *)value));
|
|
break;
|
|
case 'b':
|
|
rv = yyjson_mut_doc_ptr_set (parsed->mut_doc, ptr,
|
|
yyjson_mut_bool (parsed->mut_doc, *((int *)value) ? true : false));
|
|
break;
|
|
case 'M':
|
|
/* Parse supplied patch */
|
|
if ((patch_idoc = yyjson_read_opts ((char *)value, strlen ((char *)value), 0, &alc, NULL)))
|
|
{
|
|
if ((patch_doc = yyjson_doc_mut_copy (patch_idoc, &alc)))
|
|
{
|
|
/* Get patch target value */
|
|
if ((target_val = yyjson_mut_doc_ptr_get (parsed->mut_doc, ptr)))
|
|
{
|
|
/* Generate merged value */
|
|
if ((merged_val = yyjson_mut_merge_patch (parsed->mut_doc,
|
|
target_val,
|
|
yyjson_mut_doc_get_root (patch_doc))))
|
|
{
|
|
/* Replace value at pointer with merged value */
|
|
rv = yyjson_mut_doc_ptr_replace (parsed->mut_doc, ptr, merged_val);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ms_log (2, "%s() Cannot parse JSON Merge Patch: %s'\n", __func__, (char *)value);
|
|
}
|
|
|
|
yyjson_doc_free(patch_idoc);
|
|
yyjson_mut_doc_free (patch_doc);
|
|
|
|
break;
|
|
case 'V':
|
|
rv = yyjson_mut_doc_ptr_set (parsed->mut_doc, ptr,
|
|
yyjson_mut_val_mut_copy (parsed->mut_doc, (yyjson_mut_val *)value));
|
|
break;
|
|
case 'A':
|
|
/* Search for existing array, create if necessary */
|
|
if ((array_val = yyjson_mut_doc_ptr_get (parsed->mut_doc, ptr)) == NULL)
|
|
{
|
|
if ((array_val = yyjson_mut_arr (parsed->mut_doc)))
|
|
{
|
|
if (yyjson_mut_doc_ptr_set (parsed->mut_doc, ptr, array_val) == false)
|
|
{
|
|
array_val = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Append supplied value, will return false if array_val == NULL */
|
|
rv = yyjson_mut_arr_append (array_val,
|
|
yyjson_mut_val_mut_copy (parsed->mut_doc, (yyjson_mut_val *)value));
|
|
break;
|
|
default:
|
|
ms_log (2, "%s() Unrecognized value type '%d'\n", __func__, type);
|
|
rv = false;
|
|
}
|
|
|
|
/* Serialized extra headers and free parse state if not being retained */
|
|
if (parsestate == NULL)
|
|
{
|
|
mseh_serialize (msr, &parsed);
|
|
mseh_free_parsestate (&parsed);
|
|
}
|
|
/* If changes were applied, the immutable form of the document is now invalid */
|
|
else if (rv ==true && parsed->doc != NULL)
|
|
{
|
|
yyjson_doc_free (parsed->doc);
|
|
parsed->doc = NULL;
|
|
}
|
|
|
|
return (rv == true) ? 0 : MS_GENERROR;
|
|
} /* End of mseh_set_ptr_r() */
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Add event detection to the extra headers of the given record.
|
|
*
|
|
* If \a ptr is NULL, the default is \c '/FDSN/Event/Detection'.
|
|
*
|
|
* @param[in] msr Parsed miniSEED record to query
|
|
* @param[in] ptr Header value desired, specified in dot notation
|
|
* @param[in] eventdetection Structure with event detection values
|
|
* @param[in] parsestate Parsed state for multiple operations, can be NULL
|
|
*
|
|
* @returns 0 on success, otherwise a (negative) libmseed error code.
|
|
*
|
|
* \ref MessageOnError - this function logs a message on error
|
|
***************************************************************************/
|
|
int
|
|
mseh_add_event_detection_r (MS3Record *msr, const char *ptr,
|
|
MSEHEventDetection *eventdetection,
|
|
LM_PARSED_JSON **parsestate)
|
|
{
|
|
yyjson_mut_val entry;
|
|
yyjson_mut_val type_key, type;
|
|
yyjson_mut_val sigamp_key, sigamp;
|
|
yyjson_mut_val sigper_key, sigper;
|
|
yyjson_mut_val bgest_key, bgest;
|
|
yyjson_mut_val wave_key, wave;
|
|
yyjson_mut_val units_key, units;
|
|
yyjson_mut_val onset_key, onset;
|
|
yyjson_mut_val medsnr_key, medsnr;
|
|
yyjson_mut_val medsnr_value[6];
|
|
yyjson_mut_val medlookback_key, medlookback;
|
|
yyjson_mut_val medpickalg_key, medpickalg;
|
|
yyjson_mut_val detector_key, detector;
|
|
|
|
char timestring[40];
|
|
int idx;
|
|
|
|
if (!msr || !eventdetection)
|
|
{
|
|
ms_log (2, "%s() Required argument not defined: 'msr' or 'eventdetection'\n", __func__);
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
yyjson_mut_set_obj (&entry);
|
|
|
|
/* Add elements to new object */
|
|
if (eventdetection->type[0])
|
|
{
|
|
yyjson_mut_set_str (&type_key, "Type");
|
|
yyjson_mut_set_str (&type, eventdetection->type);
|
|
yyjson_mut_obj_add (&entry, &type_key, &type);
|
|
}
|
|
if (eventdetection->signalamplitude != 0.0)
|
|
{
|
|
yyjson_mut_set_str (&sigamp_key, "SignalAmplitude");
|
|
yyjson_mut_set_real (&sigamp, eventdetection->signalamplitude);
|
|
yyjson_mut_obj_add (&entry, &sigamp_key, &sigamp);
|
|
}
|
|
if (eventdetection->signalperiod != 0.0)
|
|
{
|
|
yyjson_mut_set_str (&sigper_key, "SignalPeriod");
|
|
yyjson_mut_set_real (&sigper, eventdetection->signalperiod);
|
|
yyjson_mut_obj_add (&entry, &sigper_key, &sigper);
|
|
}
|
|
if (eventdetection->backgroundestimate != 0.0)
|
|
{
|
|
yyjson_mut_set_str (&bgest_key, "BackgroundEstimate");
|
|
yyjson_mut_set_real (&bgest, eventdetection->backgroundestimate);
|
|
yyjson_mut_obj_add (&entry, &bgest_key, &bgest);
|
|
}
|
|
if (eventdetection->wave[0])
|
|
{
|
|
yyjson_mut_set_str (&wave_key, "Wave");
|
|
yyjson_mut_set_str (&wave, eventdetection->wave);
|
|
yyjson_mut_obj_add (&entry, &wave_key, &wave);
|
|
}
|
|
if (eventdetection->units[0])
|
|
{
|
|
yyjson_mut_set_str (&units_key, "Units");
|
|
yyjson_mut_set_str (&units, eventdetection->units);
|
|
yyjson_mut_obj_add (&entry, &units_key, &units);
|
|
}
|
|
if (eventdetection->onsettime != NSTERROR && eventdetection->onsettime != NSTUNSET)
|
|
{
|
|
if (ms_nstime2timestr (eventdetection->onsettime, timestring, ISOMONTHDAY_Z, NANO_MICRO_NONE))
|
|
{
|
|
yyjson_mut_set_str (&onset_key, "OnsetTime");
|
|
yyjson_mut_set_str (&onset, timestring);
|
|
yyjson_mut_obj_add (&entry, &onset_key, &onset);
|
|
}
|
|
else
|
|
{
|
|
ms_log (2, "%s() Cannot create time string for %"PRId64"\n", __func__, eventdetection->onsettime);
|
|
return MS_GENERROR;
|
|
}
|
|
}
|
|
if (memcmp (eventdetection->medsnr, (uint8_t[]){0, 0, 0, 0, 0, 0}, 6))
|
|
{
|
|
yyjson_mut_set_str (&medsnr_key, "MEDSNR");
|
|
yyjson_mut_set_arr (&medsnr);
|
|
yyjson_mut_obj_add (&entry, &medsnr_key, &medsnr);
|
|
|
|
/* Array containing the 6 SNR values */
|
|
for (idx = 0; idx < 6; idx++)
|
|
{
|
|
yyjson_mut_set_uint (&(medsnr_value[idx]), eventdetection->medsnr[idx]);
|
|
yyjson_mut_arr_append (&medsnr, &(medsnr_value[idx]));
|
|
}
|
|
}
|
|
if (eventdetection->medlookback >= 0)
|
|
{
|
|
yyjson_mut_set_str (&medlookback_key, "MEDLookback");
|
|
yyjson_mut_set_sint (&medlookback, eventdetection->medlookback);
|
|
yyjson_mut_obj_add (&entry, &medlookback_key, &medlookback);
|
|
}
|
|
if (eventdetection->medpickalgorithm >= 0)
|
|
{
|
|
yyjson_mut_set_str (&medpickalg_key, "MEDPickAlgorithm");
|
|
yyjson_mut_set_sint (&medpickalg, eventdetection->medpickalgorithm);
|
|
yyjson_mut_obj_add (&entry, &medpickalg_key, &medpickalg);
|
|
}
|
|
if (eventdetection->detector[0])
|
|
{
|
|
yyjson_mut_set_str (&detector_key, "Detector");
|
|
yyjson_mut_set_str (&detector, eventdetection->detector);
|
|
yyjson_mut_obj_add (&entry, &detector_key, &detector);
|
|
}
|
|
|
|
/* Add new object to array, created 'value' will be free'd on successful return */
|
|
if (mseh_set_ptr_r (msr, (ptr) ? ptr : "/FDSN/Event/Detection", &entry, 'A', parsestate))
|
|
{
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
return 0;
|
|
} /* End of mseh_add_event_detection_r() */
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Add calibration to the extra headers of the given record.
|
|
*
|
|
* If \a ptr is NULL, the default is \c '/FDSN/Calibration/Sequence'.
|
|
*
|
|
* @param[in] msr Parsed miniSEED record to query
|
|
* @param[in] ptr Header value desired, specified in dot notation
|
|
* @param[in] calibration Structure with calibration values
|
|
* @param[in] parsestate Parsed state for multiple operations, can be NULL
|
|
*
|
|
* @returns 0 on success, otherwise a (negative) libmseed error code.
|
|
*
|
|
* \ref MessageOnError - this function logs a message on error
|
|
***************************************************************************/
|
|
int
|
|
mseh_add_calibration_r (MS3Record *msr, const char *ptr,
|
|
MSEHCalibration *calibration,
|
|
LM_PARSED_JSON **parsestate)
|
|
{
|
|
yyjson_mut_val entry;
|
|
yyjson_mut_val type_key, type;
|
|
yyjson_mut_val begintime_key, begintime;
|
|
yyjson_mut_val endtime_key, endtime;
|
|
yyjson_mut_val steps_key, steps;
|
|
yyjson_mut_val firstpp_key, firstpp;
|
|
yyjson_mut_val altsign_key, altsign;
|
|
yyjson_mut_val trigger_key, trigger;
|
|
yyjson_mut_val continued_key, continued;
|
|
yyjson_mut_val amplitude_key, amplitude;
|
|
yyjson_mut_val inputunits_key, inputunits;
|
|
yyjson_mut_val amprange_key, amprange;
|
|
yyjson_mut_val duration_key, duration;
|
|
yyjson_mut_val sineperiod_key, sineperiod;
|
|
yyjson_mut_val stepbetween_key, stepbetween;
|
|
yyjson_mut_val inputchannel_key, inputchannel;
|
|
yyjson_mut_val refamp_key, refamp;
|
|
yyjson_mut_val coupling_key, coupling;
|
|
yyjson_mut_val rolloff_key, rolloff;
|
|
yyjson_mut_val noise_key, noise;
|
|
|
|
char beginstring[40];
|
|
char endstring[40];
|
|
|
|
if (!msr || !calibration)
|
|
{
|
|
ms_log (2, "%s() Required argument not defined: 'msr' or 'calibration'\n", __func__);
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
yyjson_mut_set_obj (&entry);
|
|
|
|
/* Add elements to new object */
|
|
if (calibration->type[0])
|
|
{
|
|
yyjson_mut_set_str (&type_key, "Type");
|
|
yyjson_mut_set_str (&type, calibration->type);
|
|
yyjson_mut_obj_add (&entry, &type_key, &type);
|
|
}
|
|
if (calibration->begintime != NSTERROR && calibration->begintime != NSTUNSET)
|
|
{
|
|
if (ms_nstime2timestr (calibration->begintime, beginstring, ISOMONTHDAY_Z, NANO_MICRO_NONE))
|
|
{
|
|
yyjson_mut_set_str (&begintime_key, "BeginTime");
|
|
yyjson_mut_set_str (&begintime, beginstring);
|
|
yyjson_mut_obj_add (&entry, &begintime_key, &begintime);
|
|
}
|
|
else
|
|
{
|
|
ms_log (2, "%s() Cannot create time string for %" PRId64 "\n", __func__, calibration->begintime);
|
|
return MS_GENERROR;
|
|
}
|
|
}
|
|
if (calibration->endtime != NSTERROR && calibration->endtime != NSTUNSET)
|
|
{
|
|
if (ms_nstime2timestr (calibration->endtime, endstring, ISOMONTHDAY_Z, NANO_MICRO_NONE))
|
|
{
|
|
yyjson_mut_set_str (&endtime_key, "EndTime");
|
|
yyjson_mut_set_str (&endtime, endstring);
|
|
yyjson_mut_obj_add (&entry, &endtime_key, &endtime);
|
|
}
|
|
else
|
|
{
|
|
ms_log (2, "%s() Cannot create time string for %" PRId64 "\n", __func__, calibration->endtime);
|
|
return MS_GENERROR;
|
|
}
|
|
}
|
|
if (calibration->steps >= 0)
|
|
{
|
|
yyjson_mut_set_str (&steps_key, "Steps");
|
|
yyjson_mut_set_sint (&steps, calibration->steps);
|
|
yyjson_mut_obj_add (&entry, &steps_key, &steps);
|
|
}
|
|
if (calibration->firstpulsepositive >= 0)
|
|
{
|
|
yyjson_mut_set_str (&firstpp_key, "StepFirstPulsePositive");
|
|
yyjson_mut_set_bool (&firstpp, calibration->firstpulsepositive);
|
|
yyjson_mut_obj_add (&entry, &firstpp_key, &firstpp);
|
|
}
|
|
if (calibration->alternatesign >= 0)
|
|
{
|
|
yyjson_mut_set_str (&altsign_key, "StepAlternateSign");
|
|
yyjson_mut_set_bool (&altsign, calibration->alternatesign);
|
|
yyjson_mut_obj_add (&entry, &altsign_key, &altsign);
|
|
}
|
|
if (calibration->trigger[0])
|
|
{
|
|
yyjson_mut_set_str (&trigger_key, "Trigger");
|
|
yyjson_mut_set_str (&trigger, calibration->trigger);
|
|
yyjson_mut_obj_add (&entry, &trigger_key, &trigger);
|
|
}
|
|
if (calibration->continued >= 0)
|
|
{
|
|
yyjson_mut_set_str (&continued_key, "Continued");
|
|
yyjson_mut_set_bool (&continued, calibration->continued);
|
|
yyjson_mut_obj_add (&entry, &continued_key, &continued);
|
|
}
|
|
if (calibration->amplitude != 0.0)
|
|
{
|
|
yyjson_mut_set_str (&litude_key, "Amplitude");
|
|
yyjson_mut_set_real (&litude, calibration->amplitude);
|
|
yyjson_mut_obj_add (&entry, &litude_key, &litude);
|
|
}
|
|
if (calibration->inputunits[0])
|
|
{
|
|
yyjson_mut_set_str (&inputunits_key, "InputUnits");
|
|
yyjson_mut_set_str (&inputunits, calibration->inputunits);
|
|
yyjson_mut_obj_add (&entry, &inputunits_key, &inputunits);
|
|
}
|
|
if (calibration->amplituderange[0])
|
|
{
|
|
yyjson_mut_set_str (&range_key, "AmplitudeRange");
|
|
yyjson_mut_set_str (&range, calibration->amplituderange);
|
|
yyjson_mut_obj_add (&entry, &range_key, &range);
|
|
}
|
|
if (calibration->duration != 0.0)
|
|
{
|
|
yyjson_mut_set_str (&duration_key, "Duration");
|
|
yyjson_mut_set_real (&duration, calibration->duration);
|
|
yyjson_mut_obj_add (&entry, &duration_key, &duration);
|
|
}
|
|
if (calibration->sineperiod != 0.0)
|
|
{
|
|
yyjson_mut_set_str (&sineperiod_key, "SinePeriod");
|
|
yyjson_mut_set_real (&sineperiod, calibration->sineperiod);
|
|
yyjson_mut_obj_add (&entry, &sineperiod_key, &sineperiod);
|
|
}
|
|
if (calibration->stepbetween != 0.0)
|
|
{
|
|
yyjson_mut_set_str (&stepbetween_key, "StepBetween");
|
|
yyjson_mut_set_real (&stepbetween, calibration->stepbetween);
|
|
yyjson_mut_obj_add (&entry, &stepbetween_key, &stepbetween);
|
|
}
|
|
if (calibration->inputchannel[0])
|
|
{
|
|
yyjson_mut_set_str (&inputchannel_key, "InputChannel");
|
|
yyjson_mut_set_str (&inputchannel, calibration->inputchannel);
|
|
yyjson_mut_obj_add (&entry, &inputchannel_key, &inputchannel);
|
|
}
|
|
if (calibration->refamplitude != 0.0)
|
|
{
|
|
yyjson_mut_set_str (&refamp_key, "ReferenceAmplitude");
|
|
yyjson_mut_set_real (&refamp, calibration->refamplitude);
|
|
yyjson_mut_obj_add (&entry, &refamp_key, &refamp);;
|
|
}
|
|
if (calibration->coupling[0])
|
|
{
|
|
yyjson_mut_set_str (&coupling_key, "Coupling");
|
|
yyjson_mut_set_str (&coupling, calibration->coupling);
|
|
yyjson_mut_obj_add (&entry, &coupling_key, &coupling);
|
|
}
|
|
if (calibration->rolloff[0])
|
|
{
|
|
yyjson_mut_set_str (&rolloff_key, "Rolloff");
|
|
yyjson_mut_set_str (&rolloff, calibration->rolloff);
|
|
yyjson_mut_obj_add (&entry, &rolloff_key, &rolloff);
|
|
}
|
|
if (calibration->noise[0])
|
|
{
|
|
yyjson_mut_set_str (&noise_key, "Noise");
|
|
yyjson_mut_set_str (&noise, calibration->noise);
|
|
yyjson_mut_obj_add (&entry, &noise_key, &noise);
|
|
}
|
|
|
|
/* Add new object to array, created 'value' will be free'd on successful return */
|
|
if (mseh_set_ptr_r (msr, (ptr) ? ptr : "/FDSN/Calibration/Sequence", &entry, 'A', parsestate))
|
|
{
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
return 0;
|
|
} /* End of mseh_add_calibration_r() */
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Add timing exception to the extra headers of the given record.
|
|
*
|
|
* If \a ptr is NULL, the default is \c '/FDSN/Time/Exception'.
|
|
*
|
|
* @param[in] msr Parsed miniSEED record to query
|
|
* @param[in] ptr Header value desired, specified in dot notation
|
|
* @param[in] exception Structure with timing exception values
|
|
* @param[in] parsestate Parsed state for multiple operations, can be NULL
|
|
*
|
|
* @returns 0 on success, otherwise a (negative) libmseed error code.
|
|
*
|
|
* \ref MessageOnError - this function logs a message on error
|
|
***************************************************************************/
|
|
int
|
|
mseh_add_timing_exception_r (MS3Record *msr, const char *ptr,
|
|
MSEHTimingException *exception,
|
|
LM_PARSED_JSON **parsestate)
|
|
{
|
|
yyjson_mut_val entry;
|
|
yyjson_mut_val etime_key, etime;
|
|
yyjson_mut_val vcocorr_key, vcocorr;
|
|
yyjson_mut_val recqual_key, recqual;
|
|
yyjson_mut_val count_key, count;
|
|
yyjson_mut_val type_key, type;
|
|
yyjson_mut_val clockstatus_key, clockstatus;
|
|
|
|
char timestring[40];
|
|
|
|
if (!msr || !exception)
|
|
{
|
|
ms_log (2, "%s() Required argument not defined: 'msr' or 'exception'\n", __func__);
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
yyjson_mut_set_obj (&entry);
|
|
|
|
/* Add elements to new object */
|
|
if (exception->time != NSTERROR && exception->time != NSTUNSET)
|
|
{
|
|
if (ms_nstime2timestr (exception->time, timestring, ISOMONTHDAY_Z, NANO_MICRO_NONE))
|
|
{
|
|
yyjson_mut_set_str (&etime_key, "Time");
|
|
yyjson_mut_set_str (&etime, timestring);
|
|
yyjson_mut_obj_add (&entry, &etime_key, &etime);
|
|
}
|
|
else
|
|
{
|
|
ms_log (2, "%s() Cannot create time string for %" PRId64 "\n", __func__, exception->time);
|
|
return MS_GENERROR;
|
|
}
|
|
}
|
|
if (exception->vcocorrection >= 0.0)
|
|
{
|
|
yyjson_mut_set_str (&vcocorr_key, "VCOCorrection");
|
|
yyjson_mut_set_real (&vcocorr, exception->vcocorrection);
|
|
yyjson_mut_obj_add (&entry, &vcocorr_key, &vcocorr);
|
|
}
|
|
if (exception->receptionquality >= 0)
|
|
{
|
|
yyjson_mut_set_str (&recqual_key, "ReceptionQuality");
|
|
yyjson_mut_set_sint (&recqual, exception->receptionquality);
|
|
yyjson_mut_obj_add (&entry, &recqual_key, &recqual);
|
|
}
|
|
if (exception->count > 0)
|
|
{
|
|
yyjson_mut_set_str (&count_key, "Count");
|
|
yyjson_mut_set_sint (&count, exception->count);
|
|
yyjson_mut_obj_add (&entry, &count_key, &count);
|
|
}
|
|
if (exception->type[0])
|
|
{
|
|
yyjson_mut_set_str (&type_key, "Type");
|
|
yyjson_mut_set_str (&type, exception->type);
|
|
yyjson_mut_obj_add (&entry, &type_key, &type);
|
|
}
|
|
if (exception->clockstatus[0])
|
|
{
|
|
yyjson_mut_set_str (&clockstatus_key, "ClockStatus");
|
|
yyjson_mut_set_str (&clockstatus, exception->clockstatus);
|
|
yyjson_mut_obj_add (&entry, &clockstatus_key, &clockstatus);
|
|
}
|
|
|
|
/* Add new object to array, created 'value' will be free'd on successful return */
|
|
if (mseh_set_ptr_r (msr, (ptr) ? ptr : "/FDSN/Time/Exception", &entry, 'A', parsestate))
|
|
{
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
return 0;
|
|
} /* End of mseh_add_timing_exception_r() */
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Add recenter event to the extra headers of the given record.
|
|
*
|
|
* If \a ptr is NULL, the default is \c '/FDSN/Recenter/Sequence'.
|
|
*
|
|
* @param[in] msr Parsed miniSEED record to query
|
|
* @param[in] ptr Header value desired, specified in dot notation
|
|
* @param[in] recenter Structure with recenter values
|
|
* @param[in] parsestate Parsed state for multiple operations, can be NULL
|
|
*
|
|
* @returns 0 on success, otherwise a (negative) libmseed error code.
|
|
*
|
|
* \ref MessageOnError - this function logs a message on error
|
|
***************************************************************************/
|
|
int
|
|
mseh_add_recenter_r (MS3Record *msr, const char *ptr, MSEHRecenter *recenter,
|
|
LM_PARSED_JSON **parsestate)
|
|
{
|
|
yyjson_mut_val entry;
|
|
yyjson_mut_val type_key, type;
|
|
yyjson_mut_val begin_key, begin;
|
|
yyjson_mut_val end_key, end;
|
|
yyjson_mut_val trigger_key, trigger;
|
|
char beginstring[40];
|
|
char endstring[40];
|
|
|
|
if (!msr || !recenter)
|
|
{
|
|
ms_log (2, "%s() Required argument not defined: 'msr' or 'recenter'\n", __func__);
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
yyjson_mut_set_obj (&entry);
|
|
|
|
/* Add elements to new object */
|
|
if (recenter->type[0])
|
|
{
|
|
yyjson_mut_set_str (&type_key, "Type");
|
|
yyjson_mut_set_str (&type, recenter->type);
|
|
yyjson_mut_obj_add (&entry, &type_key, &type);
|
|
}
|
|
if (recenter->begintime != NSTERROR && recenter->begintime != NSTUNSET)
|
|
{
|
|
if (ms_nstime2timestr (recenter->begintime, beginstring, ISOMONTHDAY_Z, NANO_MICRO_NONE))
|
|
{
|
|
yyjson_mut_set_str (&begin_key, "BeginTime");
|
|
yyjson_mut_set_str (&begin, beginstring);
|
|
yyjson_mut_obj_add (&entry, &begin_key, &begin);
|
|
}
|
|
else
|
|
{
|
|
ms_log (2, "%s() Cannot create time string for %" PRId64 "\n", __func__, recenter->begintime);
|
|
return MS_GENERROR;
|
|
}
|
|
}
|
|
if (recenter->endtime != NSTERROR && recenter->endtime != NSTUNSET)
|
|
{
|
|
if (ms_nstime2timestr (recenter->endtime, endstring, ISOMONTHDAY_Z, NANO_MICRO_NONE))
|
|
{
|
|
yyjson_mut_set_str (&end_key, "EndTime");
|
|
yyjson_mut_set_str (&end, endstring);
|
|
yyjson_mut_obj_add (&entry, &end_key, &end);
|
|
}
|
|
else
|
|
{
|
|
ms_log (2, "%s() Cannot create time string for %" PRId64 "\n", __func__, recenter->endtime);
|
|
return MS_GENERROR;
|
|
}
|
|
}
|
|
if (recenter->trigger[0])
|
|
{
|
|
yyjson_mut_set_str (&trigger_key, "Trigger");
|
|
yyjson_mut_set_str (&trigger, recenter->trigger);
|
|
yyjson_mut_obj_add (&entry, &trigger_key, &trigger);
|
|
}
|
|
|
|
/* Add new object to array, created 'value' will be free'd on successful return */
|
|
if (mseh_set_ptr_r (msr, (ptr) ? ptr : "/FDSN/Recenter/Sequence", &entry, 'A', parsestate))
|
|
{
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
return 0;
|
|
} /* End of mseh_add_recenter_r() */
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Generate extra headers string (serialize) from internal state
|
|
*
|
|
* Generate the extra headers JSON string from the internal parse state
|
|
* created by mseh_set_ptr_r().
|
|
*
|
|
* @param[in] msr ::MS3Record to generate extra headers for
|
|
* @param[in] parsestate Internal parsed state associated with \a msr
|
|
*
|
|
* @returns Length of extra headers on success, otherwise a (negative) libmseed error code
|
|
*
|
|
* \sa mseh_set_ptr_r()
|
|
***************************************************************************/
|
|
int
|
|
mseh_serialize (MS3Record *msr, LM_PARSED_JSON **parsestate)
|
|
{
|
|
yyjson_write_flag flg = YYJSON_WRITE_NOFLAG;
|
|
yyjson_write_err err;
|
|
yyjson_alc alc = {_priv_malloc, _priv_realloc, _priv_free, NULL};
|
|
|
|
LM_PARSED_JSON *parsed = NULL;
|
|
char *serialized = NULL;
|
|
size_t serialsize = 0;
|
|
|
|
if (!msr || !parsestate)
|
|
return MS_GENERROR;
|
|
|
|
parsed = *parsestate;
|
|
|
|
if (!parsed || !parsed->mut_doc)
|
|
return 0;
|
|
|
|
/* Serialize new JSON string */
|
|
serialized = yyjson_mut_write_opts (parsed->mut_doc, flg, &alc, &serialsize, &err);
|
|
|
|
if (serialized == NULL)
|
|
{
|
|
ms_log (2, "%s() Cannot write extra header JSON: %s\n",
|
|
__func__, (err.msg) ? err.msg : "Unknown error");
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
if (serialsize > UINT16_MAX)
|
|
{
|
|
ms_log (2, "%s() New serialization size exceeds limit of %d bytes: %" PRIu64 "\n",
|
|
__func__, UINT16_MAX, (uint64_t)serialsize);
|
|
libmseed_memory.free(serialized);
|
|
return MS_GENERROR;
|
|
}
|
|
|
|
/* Set new extra headers, replacing existing headers */
|
|
if (msr->extra)
|
|
libmseed_memory.free (msr->extra);
|
|
msr->extra = serialized;
|
|
msr->extralength = (uint16_t)serialsize;
|
|
|
|
return (int)serialsize;
|
|
}
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Free internally parsed (deserialized) JSON data
|
|
*
|
|
* Free the memory associated with JSON data parsed by mseh_get_ptr_r()
|
|
* or mseh_set_ptr_r(), specifically the data at the \a parsestate pointer.
|
|
*
|
|
* @param[in] parsestate Internal parsed state associated with \a msr
|
|
*
|
|
* \sa mseh_get_ptr_r()
|
|
* \sa mseh_set_ptr_r()
|
|
***************************************************************************/
|
|
void
|
|
mseh_free_parsestate (LM_PARSED_JSON **parsestate)
|
|
{
|
|
LM_PARSED_JSON *parsed = NULL;
|
|
|
|
if (!parsestate || !*parsestate)
|
|
return;
|
|
|
|
parsed = *parsestate;
|
|
|
|
if (parsed->doc)
|
|
yyjson_doc_free (parsed->doc);
|
|
|
|
if (parsed->mut_doc)
|
|
yyjson_mut_doc_free (parsed->mut_doc);
|
|
|
|
libmseed_memory.free(parsed);
|
|
|
|
*parsestate = NULL;
|
|
}
|
|
|
|
/**********************************************************************/ /**
|
|
* @brief Print the extra header structure for the specified MS3Record.
|
|
*
|
|
* Output is printed in a pretty, formatted form for readability and
|
|
* the anonymous, root object container (the outer {}) is not printed.
|
|
*
|
|
* @param[in] msr ::MS3Record with extra headers to pring
|
|
* @param[in] indent Number of spaces to indent output
|
|
*
|
|
* @returns 0 on success and a (negative) libmseed error code on error.
|
|
***************************************************************************/
|
|
int
|
|
mseh_print (const MS3Record *msr, int indent)
|
|
{
|
|
char *extra;
|
|
int idx;
|
|
int instring = 0;
|
|
|
|
if (!msr)
|
|
return MS_GENERROR;
|
|
|
|
if (!msr->extra || !msr->extralength)
|
|
return MS_NOERROR;
|
|
|
|
extra = msr->extra;
|
|
|
|
if (extra[0] != '{' || extra[msr->extralength - 1] != '}')
|
|
{
|
|
ms_log (1, "%s() Warning, something is wrong, extra headers are not a clean {object}\n", __func__);
|
|
}
|
|
|
|
/* Print JSON character-by-character, inserting
|
|
* indentation, spaces and newlines for readability. */
|
|
ms_log (0, "%*s", indent, "");
|
|
for (idx = 1; idx < (msr->extralength - 1); idx++)
|
|
{
|
|
/* Toggle "in string" flag for double quotes */
|
|
if (extra[idx] == '"')
|
|
instring = (instring) ? 0 : 1;
|
|
|
|
if (!instring)
|
|
{
|
|
if (extra[idx] == ':')
|
|
{
|
|
ms_log (0, ": ");
|
|
}
|
|
else if (extra[idx] == ',')
|
|
{
|
|
ms_log (0, ",\n%*s", indent, "");
|
|
}
|
|
else if (extra[idx] == '{')
|
|
{
|
|
indent += 2;
|
|
ms_log (0, "{\n%*s", indent, "");
|
|
}
|
|
else if (extra[idx] == '[')
|
|
{
|
|
indent += 2;
|
|
ms_log (0, "[\n%*s", indent, "");
|
|
}
|
|
else if (extra[idx] == '}')
|
|
{
|
|
indent -= 2;
|
|
ms_log (0, "\n%*s}", indent, "");
|
|
}
|
|
else if (extra[idx] == ']')
|
|
{
|
|
indent -= 2;
|
|
ms_log (0, "\n%*s]", indent, "");
|
|
}
|
|
else
|
|
{
|
|
ms_log (0, "%c", extra[idx]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ms_log (0, "%c", extra[idx]);
|
|
}
|
|
}
|
|
ms_log (0, "\n");
|
|
|
|
return MS_NOERROR;
|
|
} /* End of mseh_print() */
|