2008-02-01 08:33:18 +08:00
|
|
|
///////////////////////////////////////////////////////////////////////
|
2010-11-24 02:34:14 +08:00
|
|
|
// File: paramsd.cpp
|
|
|
|
// Description: Tesseract parameter Editor
|
2008-02-01 08:33:18 +08:00
|
|
|
// Author: Joern Wanke
|
|
|
|
// Created: Wed Jul 18 10:05:01 PDT 2007
|
|
|
|
//
|
|
|
|
// (C) Copyright 2007, Google Inc.
|
|
|
|
// 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.
|
|
|
|
//
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
2010-11-24 02:34:14 +08:00
|
|
|
// The parameters editor is used to edit all the parameters used within
|
2008-02-01 08:33:18 +08:00
|
|
|
// tesseract from the ui.
|
2012-03-03 01:31:24 +08:00
|
|
|
#ifdef _WIN32
|
2010-09-30 23:53:40 +08:00
|
|
|
#else
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <map>
|
2010-05-28 20:03:45 +08:00
|
|
|
|
|
|
|
// Include automatically generated configuration file if running autoconf.
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config_auto.h"
|
|
|
|
#endif
|
|
|
|
|
2008-02-01 08:33:18 +08:00
|
|
|
#ifndef GRAPHICS_DISABLED
|
2010-11-24 02:34:14 +08:00
|
|
|
#include "paramsd.h"
|
2008-02-01 08:33:18 +08:00
|
|
|
|
|
|
|
|
2010-11-24 02:34:14 +08:00
|
|
|
#include "params.h"
|
2008-02-01 08:33:18 +08:00
|
|
|
#include "scrollview.h"
|
|
|
|
#include "svmnode.h"
|
|
|
|
|
|
|
|
|
2010-11-24 02:34:14 +08:00
|
|
|
#define VARDIR "configs/" /*parameters files */
|
2008-02-01 08:33:18 +08:00
|
|
|
#define MAX_ITEMS_IN_SUBMENU 30
|
|
|
|
|
2010-11-24 02:34:14 +08:00
|
|
|
// The following variables should remain static globals, since they
|
|
|
|
// are used by debug editor, which uses a single Tesseract instance.
|
|
|
|
//
|
2008-02-01 08:33:18 +08:00
|
|
|
// Contains the mappings from unique VC ids to their actual pointers.
|
2010-11-24 02:34:14 +08:00
|
|
|
static std::map<int, ParamContent*> vcMap;
|
|
|
|
static int nrParams = 0;
|
2008-02-01 08:33:18 +08:00
|
|
|
static int writeCommands[2];
|
|
|
|
|
2010-11-24 02:34:14 +08:00
|
|
|
ELISTIZE(ParamContent)
|
2008-04-22 08:32:14 +08:00
|
|
|
|
2010-11-24 02:34:14 +08:00
|
|
|
// Constructors for the various ParamTypes.
|
|
|
|
ParamContent::ParamContent(tesseract::StringParam* it) {
|
|
|
|
my_id_ = nrParams;
|
|
|
|
nrParams++;
|
|
|
|
param_type_ = VT_STRING;
|
2008-02-01 08:33:18 +08:00
|
|
|
sIt = it;
|
|
|
|
vcMap[my_id_] = this;
|
|
|
|
}
|
2010-11-24 02:34:14 +08:00
|
|
|
// Constructors for the various ParamTypes.
|
|
|
|
ParamContent::ParamContent(tesseract::IntParam* it) {
|
|
|
|
my_id_ = nrParams;
|
|
|
|
nrParams++;
|
|
|
|
param_type_ = VT_INTEGER;
|
2008-02-01 08:33:18 +08:00
|
|
|
iIt = it;
|
|
|
|
vcMap[my_id_] = this;
|
|
|
|
}
|
2010-11-24 02:34:14 +08:00
|
|
|
// Constructors for the various ParamTypes.
|
|
|
|
ParamContent::ParamContent(tesseract::BoolParam* it) {
|
|
|
|
my_id_ = nrParams;
|
|
|
|
nrParams++;
|
|
|
|
param_type_ = VT_BOOLEAN;
|
2008-02-01 08:33:18 +08:00
|
|
|
bIt = it;
|
|
|
|
vcMap[my_id_] = this;
|
|
|
|
}
|
2010-11-24 02:34:14 +08:00
|
|
|
// Constructors for the various ParamTypes.
|
|
|
|
ParamContent::ParamContent(tesseract::DoubleParam* it) {
|
|
|
|
my_id_ = nrParams;
|
|
|
|
nrParams++;
|
|
|
|
param_type_ = VT_DOUBLE;
|
2008-02-01 08:33:18 +08:00
|
|
|
dIt = it;
|
|
|
|
vcMap[my_id_] = this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Gets a VC object identified by its ID.
|
2010-11-24 02:34:14 +08:00
|
|
|
ParamContent* ParamContent::GetParamContentById(int id) {
|
2008-02-01 08:33:18 +08:00
|
|
|
return vcMap[id];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy the first N words from the source string to the target string.
|
|
|
|
// Words are delimited by "_".
|
2010-11-24 02:34:14 +08:00
|
|
|
void ParamsEditor::GetFirstWords(
|
2008-02-01 08:33:18 +08:00
|
|
|
const char *s, // source string
|
|
|
|
int n, // number of words
|
|
|
|
char *t // target string
|
|
|
|
) {
|
|
|
|
int full_length = strlen(s);
|
|
|
|
int reqd_len = 0; // No. of chars requird
|
|
|
|
const char *next_word = s;
|
|
|
|
|
|
|
|
while ((n > 0) && reqd_len < full_length) {
|
|
|
|
reqd_len += strcspn(next_word, "_") + 1;
|
|
|
|
next_word += reqd_len;
|
|
|
|
n--;
|
|
|
|
}
|
|
|
|
strncpy(t, s, reqd_len);
|
|
|
|
t[reqd_len] = '\0'; // ensure null terminal
|
|
|
|
}
|
|
|
|
|
|
|
|
// Getter for the name.
|
2010-11-24 02:34:14 +08:00
|
|
|
const char* ParamContent::GetName() const {
|
|
|
|
if (param_type_ == VT_INTEGER) { return iIt->name_str(); }
|
|
|
|
else if (param_type_ == VT_BOOLEAN) { return bIt->name_str(); }
|
|
|
|
else if (param_type_ == VT_DOUBLE) { return dIt->name_str(); }
|
|
|
|
else if (param_type_ == VT_STRING) { return sIt->name_str(); }
|
2008-02-01 08:33:18 +08:00
|
|
|
else
|
2010-11-24 02:34:14 +08:00
|
|
|
return "ERROR: ParamContent::GetName()";
|
2008-02-01 08:33:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Getter for the description.
|
2010-11-24 02:34:14 +08:00
|
|
|
const char* ParamContent::GetDescription() const {
|
|
|
|
if (param_type_ == VT_INTEGER) { return iIt->info_str(); }
|
|
|
|
else if (param_type_ == VT_BOOLEAN) { return bIt->info_str(); }
|
|
|
|
else if (param_type_ == VT_DOUBLE) { return dIt->info_str(); }
|
|
|
|
else if (param_type_ == VT_STRING) { return sIt->info_str(); }
|
2008-02-01 08:33:18 +08:00
|
|
|
else return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Getter for the value.
|
2014-04-24 08:06:36 +08:00
|
|
|
STRING ParamContent::GetValue() const {
|
|
|
|
STRING result;
|
2010-11-24 02:34:14 +08:00
|
|
|
if (param_type_ == VT_INTEGER) {
|
2014-04-24 08:06:36 +08:00
|
|
|
result.add_str_int("", *iIt);
|
2010-11-24 02:34:14 +08:00
|
|
|
} else if (param_type_ == VT_BOOLEAN) {
|
2014-04-24 08:06:36 +08:00
|
|
|
result.add_str_int("", *bIt);
|
2010-11-24 02:34:14 +08:00
|
|
|
} else if (param_type_ == VT_DOUBLE) {
|
2014-04-24 08:06:36 +08:00
|
|
|
result.add_str_double("", *dIt);
|
2010-11-24 02:34:14 +08:00
|
|
|
} else if (param_type_ == VT_STRING) {
|
2008-02-01 08:33:18 +08:00
|
|
|
if (((STRING) * (sIt)).string() != NULL) {
|
2014-04-24 08:06:36 +08:00
|
|
|
result = sIt->string();
|
2008-02-01 08:33:18 +08:00
|
|
|
} else {
|
2014-04-24 08:06:36 +08:00
|
|
|
result = "Null";
|
2008-02-01 08:33:18 +08:00
|
|
|
}
|
|
|
|
}
|
2014-04-24 08:06:36 +08:00
|
|
|
return result;
|
2008-02-01 08:33:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Setter for the value.
|
2010-11-24 02:34:14 +08:00
|
|
|
void ParamContent::SetValue(const char* val) {
|
2008-02-01 08:33:18 +08:00
|
|
|
// TODO (wanke) Test if the values actually are properly converted.
|
|
|
|
// (Quickly visible impacts?)
|
|
|
|
changed_ = TRUE;
|
2010-11-24 02:34:14 +08:00
|
|
|
if (param_type_ == VT_INTEGER) {
|
2008-02-01 08:33:18 +08:00
|
|
|
iIt->set_value(atoi(val));
|
2010-11-24 02:34:14 +08:00
|
|
|
} else if (param_type_ == VT_BOOLEAN) {
|
2008-02-01 08:33:18 +08:00
|
|
|
bIt->set_value(atoi(val));
|
2010-11-24 02:34:14 +08:00
|
|
|
} else if (param_type_ == VT_DOUBLE) {
|
2008-02-01 08:33:18 +08:00
|
|
|
dIt->set_value(strtod(val, NULL));
|
2010-11-24 02:34:14 +08:00
|
|
|
} else if (param_type_ == VT_STRING) {
|
2008-02-01 08:33:18 +08:00
|
|
|
sIt->set_value(val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Gets the up to the first 3 prefixes from s (split by _).
|
|
|
|
// For example, tesseract_foo_bar will be split into tesseract,foo and bar.
|
2010-11-24 02:34:14 +08:00
|
|
|
void ParamsEditor::GetPrefixes(const char* s, STRING* level_one,
|
|
|
|
STRING* level_two,
|
|
|
|
STRING* level_three) {
|
2008-02-01 08:33:18 +08:00
|
|
|
char* p = new char[1024];
|
|
|
|
GetFirstWords(s, 1, p);
|
2008-04-22 08:32:14 +08:00
|
|
|
*level_one = p;
|
2008-02-01 08:33:18 +08:00
|
|
|
GetFirstWords(s, 2, p);
|
2008-04-22 08:32:14 +08:00
|
|
|
*level_two = p;
|
2008-02-01 08:33:18 +08:00
|
|
|
GetFirstWords(s, 3, p);
|
2008-04-22 08:32:14 +08:00
|
|
|
*level_three = p;
|
2008-02-01 08:33:18 +08:00
|
|
|
delete[] p;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compare two VC objects by their name.
|
2010-11-24 02:34:14 +08:00
|
|
|
int ParamContent::Compare(const void* v1, const void* v2) {
|
|
|
|
const ParamContent* one =
|
|
|
|
*reinterpret_cast<const ParamContent* const *>(v1);
|
|
|
|
const ParamContent* two =
|
|
|
|
*reinterpret_cast<const ParamContent* const *>(v2);
|
2008-04-22 08:32:14 +08:00
|
|
|
return strcmp(one->GetName(), two->GetName());
|
2008-02-01 08:33:18 +08:00
|
|
|
}
|
|
|
|
|
2010-11-24 02:34:14 +08:00
|
|
|
// Find all editable parameters used within tesseract and create a
|
2008-02-01 08:33:18 +08:00
|
|
|
// SVMenuNode tree from it.
|
|
|
|
// TODO (wanke): This is actually sort of hackish.
|
2010-11-24 02:34:14 +08:00
|
|
|
SVMenuNode* ParamsEditor::BuildListOfAllLeaves(tesseract::Tesseract *tess) {
|
2008-02-01 08:33:18 +08:00
|
|
|
SVMenuNode* mr = new SVMenuNode();
|
2010-11-24 02:34:14 +08:00
|
|
|
ParamContent_LIST vclist;
|
|
|
|
ParamContent_IT vc_it(&vclist);
|
2009-07-11 10:03:51 +08:00
|
|
|
// Amount counts the number of entries for a specific char*.
|
|
|
|
// TODO(rays) get rid of the use of std::map.
|
|
|
|
std::map<const char*, int> amount;
|
2008-02-01 08:33:18 +08:00
|
|
|
|
2010-11-24 02:34:14 +08:00
|
|
|
// Add all parameters to a list.
|
|
|
|
int v, i;
|
|
|
|
int num_iterations = (tess->params() == NULL) ? 1 : 2;
|
|
|
|
for (v = 0; v < num_iterations; ++v) {
|
|
|
|
tesseract::ParamsVectors *vec = (v == 0) ? GlobalParams() : tess->params();
|
|
|
|
for (i = 0; i < vec->int_params.size(); ++i) {
|
|
|
|
vc_it.add_after_then_move(new ParamContent(vec->int_params[i]));
|
|
|
|
}
|
|
|
|
for (i = 0; i < vec->bool_params.size(); ++i) {
|
|
|
|
vc_it.add_after_then_move(new ParamContent(vec->bool_params[i]));
|
|
|
|
}
|
|
|
|
for (i = 0; i < vec->string_params.size(); ++i) {
|
|
|
|
vc_it.add_after_then_move(new ParamContent(vec->string_params[i]));
|
|
|
|
}
|
|
|
|
for (i = 0; i < vec->double_params.size(); ++i) {
|
|
|
|
vc_it.add_after_then_move(new ParamContent(vec->double_params[i]));
|
|
|
|
}
|
2008-02-01 08:33:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Count the # of entries starting with a specific prefix.
|
2008-04-22 08:32:14 +08:00
|
|
|
for (vc_it.mark_cycle_pt(); !vc_it.cycled_list(); vc_it.forward()) {
|
2010-11-24 02:34:14 +08:00
|
|
|
ParamContent* vc = vc_it.data();
|
2008-04-22 08:32:14 +08:00
|
|
|
STRING tag;
|
|
|
|
STRING tag2;
|
|
|
|
STRING tag3;
|
2008-02-01 08:33:18 +08:00
|
|
|
|
|
|
|
GetPrefixes(vc->GetName(), &tag, &tag2, &tag3);
|
2008-04-22 08:32:14 +08:00
|
|
|
amount[tag.string()]++;
|
|
|
|
amount[tag2.string()]++;
|
|
|
|
amount[tag3.string()]++;
|
2008-02-01 08:33:18 +08:00
|
|
|
}
|
|
|
|
|
2010-11-24 02:34:14 +08:00
|
|
|
vclist.sort(ParamContent::Compare); // Sort the list alphabetically.
|
2008-02-01 08:33:18 +08:00
|
|
|
|
|
|
|
SVMenuNode* other = mr->AddChild("OTHER");
|
|
|
|
|
|
|
|
// go through the list again and this time create the menu structure.
|
2008-04-22 08:32:14 +08:00
|
|
|
vc_it.move_to_first();
|
|
|
|
for (vc_it.mark_cycle_pt(); !vc_it.cycled_list(); vc_it.forward()) {
|
2010-11-24 02:34:14 +08:00
|
|
|
ParamContent* vc = vc_it.data();
|
2008-04-22 08:32:14 +08:00
|
|
|
STRING tag;
|
|
|
|
STRING tag2;
|
|
|
|
STRING tag3;
|
2008-02-01 08:33:18 +08:00
|
|
|
GetPrefixes(vc->GetName(), &tag, &tag2, &tag3);
|
|
|
|
|
2014-04-24 08:06:36 +08:00
|
|
|
if (amount[tag.string()] == 1) {
|
|
|
|
other->AddChild(vc->GetName(), vc->GetId(), vc->GetValue().string(),
|
|
|
|
vc->GetDescription());
|
2008-02-01 08:33:18 +08:00
|
|
|
} else { // More than one would use this submenu -> create submenu.
|
2008-04-22 08:32:14 +08:00
|
|
|
SVMenuNode* sv = mr->AddChild(tag.string());
|
2009-07-11 10:03:51 +08:00
|
|
|
if ((amount[tag.string()] <= MAX_ITEMS_IN_SUBMENU) ||
|
|
|
|
(amount[tag2.string()] <= 1)) {
|
2008-02-01 08:33:18 +08:00
|
|
|
sv->AddChild(vc->GetName(), vc->GetId(),
|
2014-04-24 08:06:36 +08:00
|
|
|
vc->GetValue().string(), vc->GetDescription());
|
2008-02-01 08:33:18 +08:00
|
|
|
} else { // Make subsubmenus.
|
2008-04-22 08:32:14 +08:00
|
|
|
SVMenuNode* sv2 = sv->AddChild(tag2.string());
|
2008-02-01 08:33:18 +08:00
|
|
|
sv2->AddChild(vc->GetName(), vc->GetId(),
|
2014-04-24 08:06:36 +08:00
|
|
|
vc->GetValue().string(), vc->GetDescription());
|
2008-02-01 08:33:18 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return mr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Event listener. Waits for SVET_POPUP events and processes them.
|
2010-11-24 02:34:14 +08:00
|
|
|
void ParamsEditor::Notify(const SVEvent* sve) {
|
2008-02-01 08:33:18 +08:00
|
|
|
if (sve->type == SVET_POPUP) { // only catch SVET_POPUP!
|
|
|
|
char* param = sve->parameter;
|
|
|
|
if (sve->command_id == writeCommands[0]) {
|
2010-11-24 02:34:14 +08:00
|
|
|
WriteParams(param, false);
|
2008-02-01 08:33:18 +08:00
|
|
|
} else if (sve->command_id == writeCommands[1]) {
|
2010-11-24 02:34:14 +08:00
|
|
|
WriteParams(param, true);
|
2008-02-01 08:33:18 +08:00
|
|
|
} else {
|
2010-11-24 02:34:14 +08:00
|
|
|
ParamContent* vc = ParamContent::GetParamContentById(
|
2008-02-01 08:33:18 +08:00
|
|
|
sve->command_id);
|
|
|
|
vc->SetValue(param);
|
|
|
|
sv_window_->AddMessage("Setting %s to %s",
|
2014-04-24 08:06:36 +08:00
|
|
|
vc->GetName(), vc->GetValue().string());
|
2008-02-01 08:33:18 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-11-24 02:34:14 +08:00
|
|
|
// Integrate the parameters editor as popupmenu into the existing scrollview
|
2008-02-01 08:33:18 +08:00
|
|
|
// window (usually the pg editor). If sv == null, create a new empty
|
2010-11-24 02:34:14 +08:00
|
|
|
// empty window and attach the parameters editor to that window (ugly).
|
|
|
|
ParamsEditor::ParamsEditor(tesseract::Tesseract* tess,
|
2009-07-11 10:03:51 +08:00
|
|
|
ScrollView* sv) {
|
2008-02-01 08:33:18 +08:00
|
|
|
if (sv == NULL) {
|
2010-11-24 02:34:14 +08:00
|
|
|
const char* name = "ParamEditorMAIN";
|
2008-02-01 08:33:18 +08:00
|
|
|
sv = new ScrollView(name, 1, 1, 200, 200, 300, 200);
|
|
|
|
}
|
|
|
|
|
|
|
|
sv_window_ = sv;
|
|
|
|
|
|
|
|
//Only one event handler per window.
|
|
|
|
//sv->AddEventHandler((SVEventHandler*) this);
|
|
|
|
|
2010-11-24 02:34:14 +08:00
|
|
|
SVMenuNode* svMenuRoot = BuildListOfAllLeaves(tess);
|
2008-02-01 08:33:18 +08:00
|
|
|
|
2010-11-24 02:34:14 +08:00
|
|
|
STRING paramfile;
|
|
|
|
paramfile = tess->datadir;
|
|
|
|
paramfile += VARDIR; // parameters dir
|
|
|
|
paramfile += "edited"; // actual name
|
2008-02-01 08:33:18 +08:00
|
|
|
|
|
|
|
SVMenuNode* std_menu = svMenuRoot->AddChild ("Build Config File");
|
|
|
|
|
2010-11-24 02:34:14 +08:00
|
|
|
writeCommands[0] = nrParams+1;
|
|
|
|
std_menu->AddChild("All Parameters", writeCommands[0],
|
|
|
|
paramfile.string(), "Config file name?");
|
2008-02-01 08:33:18 +08:00
|
|
|
|
2010-11-24 02:34:14 +08:00
|
|
|
writeCommands[1] = nrParams+2;
|
|
|
|
std_menu->AddChild ("changed_ Parameters Only", writeCommands[1],
|
|
|
|
paramfile.string(), "Config file name?");
|
2008-02-01 08:33:18 +08:00
|
|
|
|
|
|
|
svMenuRoot->BuildMenu(sv, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-11-24 02:34:14 +08:00
|
|
|
// Write all (changed_) parameters to a config file.
|
|
|
|
void ParamsEditor::WriteParams(char *filename,
|
|
|
|
bool changes_only) {
|
2008-02-01 08:33:18 +08:00
|
|
|
FILE *fp; // input file
|
|
|
|
char msg_str[255];
|
|
|
|
// if file exists
|
2011-08-12 05:42:13 +08:00
|
|
|
if ((fp = fopen (filename, "rb")) != NULL) {
|
2008-02-01 08:33:18 +08:00
|
|
|
fclose(fp);
|
|
|
|
sprintf (msg_str, "Overwrite file " "%s" "? (Y/N)", filename);
|
|
|
|
int a = sv_window_->ShowYesNoDialog(msg_str);
|
2016-11-25 22:14:46 +08:00
|
|
|
if (a == 'n') {
|
|
|
|
return;
|
|
|
|
} // don't write
|
2008-02-01 08:33:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-08-12 05:42:13 +08:00
|
|
|
fp = fopen (filename, "wb"); // can we write to it?
|
2008-02-01 08:33:18 +08:00
|
|
|
if (fp == NULL) {
|
2016-11-25 22:14:46 +08:00
|
|
|
sv_window_->AddMessage(
|
|
|
|
"Can't write to file "
|
|
|
|
"%s"
|
|
|
|
"",
|
|
|
|
filename);
|
2008-02-01 08:33:18 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-11-24 02:34:14 +08:00
|
|
|
for (std::map<int, ParamContent*>::iterator iter = vcMap.begin();
|
2008-02-01 08:33:18 +08:00
|
|
|
iter != vcMap.end();
|
|
|
|
++iter) {
|
2010-11-24 02:34:14 +08:00
|
|
|
ParamContent* cur = iter->second;
|
2008-02-01 08:33:18 +08:00
|
|
|
if (!changes_only || cur->HasChanged()) {
|
2014-04-24 08:06:36 +08:00
|
|
|
fprintf(fp, "%-25s %-12s # %s\n",
|
|
|
|
cur->GetName(), cur->GetValue().string(), cur->GetDescription());
|
2008-02-01 08:33:18 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
}
|
|
|
|
#endif
|