Add context help for PG functions.

Добавлен вызов контекстной помощи по именам функций Postgresql.
Для этого требуется:
1. в параметрах указать путь к html файлам документации.
2. выполнить скрипт _extract_func_help.pl для генерации файла _func.txt
3. разместить файл _func.txt в каталоге с документацией.

Для вызова помощи нужно выделить слово или выражение и нажать Ctrl+F1.
Можно просто нажать Ctrl+F1 и тогда для слова слева от курсора будет выведена справка.
Если слову соответсвует несколько функций они будут выведены в виде списка имен.

Для навигации в окне контекстной помощи:
 - закрыть окно или переместиться назад - правая кнопка мыши
 - выделить текст + правая кнопка - копировать выделение в буфер и закрыть окно.
 - нажатие ESC - закрытие окна.

Для отображения помощи используется  wxHtmlWindow https://docs.wxwidgets.org/latest/overview_html.html.
This commit is contained in:
lsv 2024-05-06 19:31:52 +05:00
parent 9f3cfacf96
commit 2d3f87edaa
9 changed files with 678 additions and 2 deletions

View file

@ -21,10 +21,13 @@
#include "ctl/ctlSQLBox.h"
#include "dlg/dlgFindReplace.h"
#include "frm/menu.h"
#include "frm/frmMain.h"
#include "utils/sysProcess.h"
#include <wx/clipbrd.h>
#include <wx/aui/aui.h>
#include "utils/align/AlignWrap.h"
#include "utils/popuphelp.h"
wxString ctlSQLBox::sqlKeywords;
static const wxString s_leftBrace(_T("([{"));
static const wxString s_rightBrace(_T(")]}"));
@ -43,6 +46,7 @@ wxString pgscriptKeywords = wxT(" assert break columns continue date datetime fi
BEGIN_EVENT_TABLE(ctlSQLBox, wxStyledTextCtrl)
EVT_KEY_DOWN(ctlSQLBox::OnKeyDown)
EVT_MENU(MNU_FIND, ctlSQLBox::OnSearchReplace)
EVT_MENU(MNU_FUNC_HELP, ctlSQLBox::OnFuncHelp)
EVT_MENU(MNU_COPY, ctlSQLBox::OnCopy)
EVT_MENU(MNU_AUTOCOMPLETE, ctlSQLBox::OnAutoComplete)
EVT_KILL_FOCUS(ctlSQLBox::OnKillFocus)
@ -194,11 +198,12 @@ void ctlSQLBox::Create(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons
SetFoldFlags(16);
// Setup accelerators
wxAcceleratorEntry entries[3];
wxAcceleratorEntry entries[4];
entries[0].Set(wxACCEL_CTRL, (int)'F', MNU_FIND);
entries[1].Set(wxACCEL_CTRL, WXK_SPACE, MNU_AUTOCOMPLETE);
entries[2].Set(wxACCEL_CTRL, (int)'C', MNU_COPY);
wxAcceleratorTable accel(3, entries);
entries[3].Set(wxACCEL_CTRL, WXK_F1, MNU_FUNC_HELP);
wxAcceleratorTable accel(4, entries);
SetAcceleratorTable(accel);
// Autocompletion configuration
@ -497,9 +502,41 @@ void ctlSQLBox::SetDefFunction(wxArrayString &name, wxArrayString &def) {
void ctlSQLBox::OnCopy(wxCommandEvent& ev) {
Copy();
}
void ctlSQLBox::OnFuncHelp(wxCommandEvent& ev) {
FunctionPGHelper *fh=winMain->GetFunctionPGHelper();
int pos = GetCurrentPos();
if (!fh->isValid()) return;
wxPoint p = ClientToScreen( PointFromPosition(pos));
wxString current = GetSelectedText();
wxString key = "";
if (!current.IsEmpty())
key = current;
else {
wxChar ch;
wxString tmp;
while ((pos--) >= 0)
{
ch = GetCharAt(pos);
if (ch < 35) { break; }
if ( wxIsalnum(ch) || ch=='_' || ch> 255) tmp.Append(ch);
//pos--;
}
for (int i = tmp.Len()-1; i >=0; i--) key+=tmp[i];
}
delete m_PopupHelp;
m_PopupHelp = new popuphelp(this->GetParent(), key.Lower(), fh);
if (m_PopupHelp && m_PopupHelp->IsValid()) {
m_PopupHelp->Position(p, wxSize(0, 17));
m_PopupHelp->Popup();
}
}
void ctlSQLBox::OnKeyDown(wxKeyEvent &event)
{
if (event.GetKeyCode() == WXK_ESCAPE && m_PopupHelp) { delete m_PopupHelp; m_PopupHelp = NULL; }
int pos = GetCurrentPos();
wxChar ch = GetCharAt(pos - 1);
wxChar nextch = GetCharAt(pos);

View file

@ -21,6 +21,7 @@
#include "db/pgConn.h"
#include "dlg/dlgFindReplace.h"
#include "ctl/ctlAuiNotebook.h"
#include "utils/popuphelp.h"
// These structs are from Scintilla.h which isn't easily #included :-(
struct CharacterRange
@ -57,6 +58,7 @@ public:
void OnAutoComplete(wxCommandEvent &event);
void OnSearchReplace(wxCommandEvent &event);
void OnCopy(wxCommandEvent& ev);
void OnFuncHelp(wxCommandEvent& ev);
void OnKillFocus(wxFocusEvent &event);
// void OnBackGround(wxEraseEvent &event);
void SetQueryBook(ctlAuiNotebook *query_book);
@ -121,6 +123,7 @@ private:
wxArrayString *m_def; // finction arguments
wxString list_table; // list table from section
wxString calltip;
popuphelp *m_PopupHelp=NULL;
int ct_hl;
dlgFindReplace *m_dlgFindReplace;
pgConn *m_database;

View file

@ -27,6 +27,7 @@
#include "frm/frmLog.h"
#include "ctl/ctlGitPanel.h"
#include "ctl/ctlShortCut.h"
#include "utils/FunctionPGHelper.h";
//
// This number MUST be incremented if changing any of the default perspectives
//
@ -180,6 +181,10 @@ public:
{
return pluginsMenu;
}
FunctionPGHelper * GetFunctionPGHelper()
{
return &hhelp;
}
wxString GetCurrentNodePath();
bool SetCurrentNode(wxTreeItemId node, const wxString &path);
@ -212,6 +217,7 @@ private:
#if !defined(NO_WXJSON_GIT)
ctlGitPanel* git;
#endif
FunctionPGHelper hhelp;
ctlAuiNotebook *listViews;
ctlSQLBox *sqlPane;
wxMenu *newMenu, *debuggingMenu, *reportMenu, *toolsMenu, *pluginsMenu, *viewMenu,

View file

@ -96,6 +96,7 @@ enum
MNU_CONTENTS,
MNU_HELP,
MNU_FUNC_HELP,
MNU_HINT,
MNU_CONFIGSUBMENU,

View file

@ -0,0 +1,139 @@
#ifndef FUNCTIONPGHELPER_H
#define FUNCTIONPGHELPER_H
#include <utils/sysSettings.h>
#include <wx/regex.h>
#include <map>
#include <vector>
#include <wx/stdpaths.h>
#include <wx/textfile.h>
#include <wx/filename.h>
extern sysSettings *settings;
class FunctionPGHelper
{
public:
FunctionPGHelper() {};
wxString getHelpString(wxString fnd, bool isPart = true) {
if (!isValid()) return wxEmptyString;
auto search = body.find(fnd);
wxString txt;
if (search != body.end())
txt = search->second;
else
{
std::vector<wxString> list;
int l = fnd.Len();
wxString b;
for (const auto& e : body) {
if (e.first.Len() > l && e.first.StartsWith(fnd)) {
list.push_back(e.first);
b = e.second;
}
}
if (list.size() == 1) txt = b;
else {
for (const auto& s : list) {
txt += wxString::Format("<a href=\"%s\">%s</a><br>", s, s);
}
}
}
//if (i == wxNOT_FOUND) return wxEmptyString;
return txt;
}
wxString getSqlCommandHelp(wxString fnd) {
wxUniChar sep = wxFileName::GetPathSeparator();
fnd.Replace(" ", "");
wxString f = wxFindFirstFile(path + sep+"sql-"+fnd+"*.html");
wxString last,txt;
int c = 0;
while (!f.empty())
{
f = f.AfterLast(sep);
last = f;
txt += wxString::Format("<a href=\"%s\">%s</a><br>", f, f);
f = wxFindNextFile();
c++;
}
if (last.empty()) {
return wxEmptyString;
}
else if (c==1) {
return getHelpFile(last);
}
else {
return txt;
}
}
wxString getHelpFile(wxString filename) {
wxString tempDir = path + wxFileName::GetPathSeparator() + filename;
if (!wxFileExists(tempDir)) return wxEmptyString;
wxTextFile tfile;
tfile.Open(tempDir);
// read the first line
wxString str, sbody;
sbody = tfile.GetFirstLine();
bool flag = true;
wxRegEx b("(<body .*?>)");
while (!tfile.Eof())
{
str = tfile.GetNextLine();
if (flag && b.Matches(str)) {
size_t start, len;
b.GetMatch(&start, &len, 0);
str = str.Mid(start + len);
sbody = "<body>";
flag = false;
}
sbody += str;
}
return sbody;
}
bool isValid() {
if (!isload) loadfile();
return isload;
}
private:
bool isload = false;
wxString path;
std::map<wxString, wxString> body;
void loadfile() {
if (isload) return;
body.clear();
path = settings->GetPgHelpPath();
wxString tempDir = path + "_func.txt";
//tempDir="C:\\Users\\lsv\\Source\\Repos\\wxHtmlhint\\1";
if (!wxFileExists(tempDir)) return;
wxTextFile tfile;
tfile.Open(tempDir);
// read the first line
wxString str, sbody;
wxString name = tfile.GetFirstLine();
//wxSortedArrayString names;
name = name.AfterFirst('#');
// read all lines one by one
// until the end of the file
while (!tfile.Eof())
{
str = tfile.GetNextLine();
if (str.Left(1) == '#') {
body.emplace(name, sbody);
sbody = "";
name = str.AfterFirst('#');
}
else sbody += str;
}
body.emplace(name, sbody);
isload = true;
};
};
#endif

128
include/utils/popuphelp.h Normal file
View file

@ -0,0 +1,128 @@
#ifndef POPUPHELP_H
#define POPUPHELP_H
#include "wx/popupwin.h"
#include <wx/html/htmlwin.h>
#include "wx/clipbrd.h"
#include "utils/FunctionPGHelper.h"
#include <wx/regex.h>
#include <map>
#include <vector>
class popuphelp :
public wxPopupTransientWindow
{
public:
//popuphelp(wxWindow* parent);
bool ProcessLeftDown(wxMouseEvent& event)
{
return false;
}
bool IsValid() {
return isvalid;
}
popuphelp(wxWindow* parent,wxString keyword, FunctionPGHelper *hhelper) : wxPopupTransientWindow(parent) {
SetSize(450,370);
this->hhelper = hhelper;
SetBackgroundColour(*wxBLACK);
htmlWindow = new wxHtmlWindow(this, -1, wxDefaultPosition,GetSize());
htmlWindow->SetRelatedStatusBar(0);
//htmlWindow->SetPage("<html><body><h1>TEST</h1><span fgcolor=\"#332233\">Set Page Works</span></body></hmtl>");
wxString txt = hhelper->getHelpString(keyword);
if (txt.IsEmpty()) {
txt = hhelper->getSqlCommandHelp(keyword);
if (txt.empty()) {
isvalid = false;
return;
}
}
SetPage(txt);
//wxSize sz= htmlWindow->GetSize();
//sz = htmlWindow->GetBestSize();
//htmlWindow->SetHTMLBackgroundImage(wxBitmapBundle::FromSVGFile("data/bg.svg", wxSize(65, 45)));
wxBoxSizer* topsizer;
topsizer = new wxBoxSizer(wxVERTICAL);
//htmlWindow->SetInitialSize(wxSize(htmlWindow->GetInternalRepresentation()->GetWidth(), htmlWindow->GetInternalRepresentation()->GetHeight()));
//SetSize(wxSize(300,150));
topsizer->Add(htmlWindow, 1, wxALL, 1);
//wxButton* bu1 = new wxButton(this, wxID_OK, _("OK"));
//bu1->SetDefault();
//topsizer->Add(bu1, 0, wxALL | wxALIGN_RIGHT, 15);
SetSizer(topsizer);
topsizer->Fit(this);
//this->Bind(wxEVT_HTML_CELL_CLICKED, [&](wxHtmlCellEvent& event) {
// wxHtmlCell* c = event.GetCell();
//
// wxString ctext=c->ConvertToText(NULL);
// ctext=htmlWindow->SelectionToText();
// wxString s = wxString::Format("cell = %s",ctext.c_str());
// wxMessageBox(s, "cell", wxOK | wxICON_INFORMATION);
// });
this->Bind(wxEVT_HTML_LINK_CLICKED, [&](wxHtmlLinkEvent& event) {
wxHtmlLinkInfo i = event.GetLinkInfo();
wxString name = i.GetHref();
wxString body=this->hhelper->getHelpString(name);
if (body.IsEmpty()) {
body = this->hhelper->getHelpFile(name);
}
SetPage(body);
//ctext=htmlWindow->SelectionToText();
//wxString s = wxString::Format("cell = %s",ctext.c_str());
});
htmlWindow->Bind(wxEVT_RIGHT_UP, [&](wxMouseEvent& event) {
wxString name;
//wxString body = this->hhelper->getHelpString(name);
wxString ctext = htmlWindow->SelectionToText();
if (!ctext.IsEmpty()) {
wxClipboardLocker clip;
if (!clip ||
!wxTheClipboard->AddData(new wxTextDataObject(ctext)))
{
}
Hide();
return;
}
this->SetPage("", true);
//ctext=htmlWindow->SelectionToText();
//wxString s = wxString::Format("cell = %s",ctext.c_str());
});
}
private:
bool isvalid = true;
wxHtmlWindow* htmlWindow;
FunctionPGHelper* hhelper;
std::vector<wxString> hist;
void SetPage(wxString innerbody,bool gethistory=false) {
wxString h;
int p = innerbody.Find("<body>");
if (p > -1) {
innerbody.Replace("<body>", "<html><body TEXT=\"#000000\" BGCOLOR=\"#FFFFA0\" LINK=\"#0000FF\" VLINK=\"#FF0000\" ALINK=\"#000088\">", false);
h = "" + innerbody + "";
} else
h = "<html><body TEXT=\"#000000\" BGCOLOR=\"#FFFFA0\" LINK=\"#0000FF\" VLINK=\"#FF0000\" ALINK=\"#000088\">" + innerbody + "</body></hmtl>";
if (gethistory) {
if (hist.size() < 2) {
Hide();
return;
}
hist.pop_back();
h = hist[hist.size()-1];
}
else {
hist.push_back(h);
}
htmlWindow->SetPage(h);
}
};
#endif

View file

@ -13,10 +13,17 @@
#define VERSION_H
// Application Versions
#ifdef CORP
#define VERSION_STR wxT("1.22")
#define VERSION_NUM 1,22,0,0
#define VERSION_PACKAGE 1.22.2
#else
#define VERSION_STR wxT("1.26 Dev ASUTP support PG16")
#define VERSION_NUM 1,26,0,0
#define VERSION_PACKAGE 1.26.0-dev
#endif
#define PRERELEASE 1
// #define BUILD "..."

355
utils/_extract_func_help.pl Normal file
View file

@ -0,0 +1,355 @@
#!/usr/bin/perl
use strict;
use Data::Dump qw(dump);
my $pathdir_help=".";
my @files;
my @stak;
my %function_help;
my %useref;
my %section;
my %ignorehtml=(
'pgbench.html',
);
my @keys=(
"<td class=\"func_table_entry\">"
,"</td>"
);
my %tags=(
'pclass="func_signature"' => '<span style="font-size: 11pt;">',
'pclass="func_signature"/p' => '</span>',
'p' => '<blockquote>',
'p/p' => '</blockquote>',
'ul' => '<ul>',
'ul/ul' => '</ul>',
'liclass="listitem"' => '<li>',
'liclass="listitem"/li' => '</li>',
'divclass="itemizedlist"' => '',
'divclass="itemizedlist"/div' => '',
'a' => '<a>',
'a/a' => '</a>',
'sup' => '<sup>',
'sup/sup' => '</sup>',
'em' => '<em>',
'em/em' => '</em>',
'emclass="replaceable"' => '<b>',
'emclass="replaceable"/em' => '</b>',
'emclass="lineannotation"' => '',
'emclass="lineannotation"/em' => '',
'spanclass="lineannotation"' => '<small>',
'spanclass="lineannotation"/span' => '</small>',
'acronymclass="acronym"' => '',
'acronymclass="acronym"/acronym' => '',
'spanclass="quote"' => '',
'spanclass="quote"/span' => '',
'spanclass="productname"' => '<b>',
'spanclass="productname"/span' => '</b>',
'spanclass="emphasis"' => '<b>',
'spanclass="emphasis"/span' => '</b>',
'spanclass="refentrytitle"' => '<b>',
'spanclass="refentrytitle"/span' => '</b>',
'spanclass="symbol_font"' => '<b>',
'spanclass="symbol_font"/span' => '</b>',
'code' => '',
'code/code' => '',
'preclass="programlisting"' => '<pre>',
'preclass="programlisting"/pre' => '</pre>',
'preclass="screen"' => '<pre>',
'preclass="screen"/pre' => '</pre>',
'preclass="synopsis"' => '',
'preclass="synopsis"/pre' => '',
'spanclass="optional"' => '<i>',
'spanclass="optional"/span' => '</i>',
'spanclass="systemitem"' => '<i>',
'spanclass="systemitem"/span' => '</i>',
'spanclass="application"' => '<i>',
'spanclass="application"/span' => '</i>',
'codeclass="filename"' => '<b>',
'codeclass="filename"/code' => '</b>',
'emclass="parameter"' => '',
'emclass="parameter"/em' => '',
'emclass="firstterm"' => '',
'emclass="firstterm"/em' => '',
'codeclass="varname"' => '',
'codeclass="varname"/code' => '',
'codeclass="structname"' => '<b>',
'codeclass="structname"/code' => '</b>',
'codeclass="structfield"' => '<b>',
'codeclass="structfield"/code' => '</b>',
'codeclass="literal"' => '<b>',
'codeclass="literal"/code' => '</b>',
'codeclass="function"' => '<b>',
'codeclass="function"/code' => '</b>',
'codeclass="returnvalue"' => '<b>',
'codeclass="returnvalue"/code' => '</b>',
'codeclass="type"' => '<b>',
'codeclass="type"/code' => '</b>',
'codeclass="command"' => '<b>',
'codeclass="command"/code' => '</b>',
);
opendir(DIR, $pathdir_help) or die "can't opendir $pathdir_help: $!";
while (defined(my $file = readdir(DIR))) {
next if (!($file =~ /html$/));
push @files, $file;
}
closedir(DIR);
my $TDcount=0;
foreach my $file (sort @files) {
#open my $info, $file or die "Could not open $file: $!";
if (exists $ignorehtml{$file}) {next;}
my $fileContent;
open(my $F, '<', $file) or die $!;
binmode($F);
{
local $/;
$fileContent = <$F>;
}
close($F);
my $pos=0;
my $lvl=0;
my $cc=0;
my $val="";
my $se="div";
#div id=href
$pos=0;
while ($pos>-1) {
my $n1=index($fileContent,"id=\"",$pos);
if ($n1 > -1) {
my $n2=index($fileContent,"\"",$n1+4);
my $id_name=substr($fileContent,$n1+4,$n2-$n1-4);
my $nn=$n1;
while (substr($fileContent,$nn,1) ne "<") {
$nn--;
}
$nn++;
($se)= substr($fileContent,$nn,40) =~ /(\S+)/;
#print "se=$se id_name=$id_name tag=\n40 sim=".substr($fileContent,$nn,40);
$n2++;
$pos=$n2;
#if (exists $ref{$id_name})
if (uc($id_name) eq $id_name)
{
my @st=();
my $n3=$pos;
while ($n1 > -1) {
$n1=$pos;
$n1=index($fileContent,$se,$pos);
if ($n1 >-1) {
#print "$n1\n";
if (substr($fileContent,$n1-1,1) eq "<") {
# <div>
push @st, $n1;
#print "+".substr($fileContent,$n1,20)."\n";
$n1=$n1+3;
$pos=$n1;
next;
}
if (substr($fileContent,$n1-1,1) eq "/" && substr($fileContent,$n1-2,1) eq "<") {
#print "-".substr($fileContent,$n1,20)."\n";
if (scalar @st == 0) {
if ($se eq "dt") {
$n1=index($fileContent,"</dd>",$n1);
$n1=$n1+7;
#print "".substr($fileContent,$n3+1,$n1-$n3-length($se)-1);
}
my $r=substr($fileContent,$n3+1,$n1-$n3-length($se)-1);
$section{$id_name}=$r;
#print "$r \n$id_name\n";
#exit;
$n1=-1;
next;
}
my $nnn=pop @st;
$pos=$n1+length($se);
next;
}
$pos=$n1+length($se);
}
}
$n2=$n2+1;
print "";
}
$pos=$n2;
} else { $pos=$n1;}
}
#td func
#print "file $file\n";
$pos=0;
while( $pos > -1) {
#td function
my $ist=index($fileContent,$keys[$lvl],$pos);
if ($ist > -1) {
my $ien=index($fileContent,$keys[$lvl+1],$ist);
if ($ien >-1) {
my $posval=$ist+length($keys[$lvl]);
$val=substr($fileContent,$posval,$ien-$posval);
print "Read file $file .. " if $cc == 0;
parseTag($val);
$pos=$ien+length($keys[$lvl+1]);
$cc++;
} else {
die "Not closed tag TD. File : $file position : $ist\n";
}
} else {
$pos=$ist;
}
}
print " Ok.\n" if $cc > 0;
}
print " TD count $TDcount\n";
open(F, '>', "_func.txt") or die $!;
my $c0=0;
foreach my $key (sort keys %useref) {
$function_help{$key}=$section{$key};
#print "section $key\n";
$c0++;
}
print "Add section $c0\n";
foreach my $key (sort keys %function_help) {
#print "$key: $function_help{$key}\n";
print F "#".$key."\n".$function_help{$key}."\n";
}
close(F);
exit;
sub parseTag
{
my $s=shift;
$TDcount++;
my $text;
my $tagname; my $attr; my $vv;
my $pos=0;
my $repl; my $pc;
my $pre=0;
my $fn=0;
my $flit=0;
my $fname;
my $fliteral;
my $href;
my @t=();
while ($pos <length($s) && $s ne "") {
if (substr($s,$pos,1) eq "<") {
if (substr($s,$pos,2) eq "</") {
# close tag
$tagname = pop @t;
my $tmp=substr($s,$pos);
# print "CLOSE TAG=$tagname pos=$pos\n";
my ($tagnameC) = $tmp =~ m/<(\/\w+)>/g;
if (substr($tagname,0,3) eq "pre") {$pre=0;}
$pos=index($s,'>',$pos)+1;
if (exists $tags{$tagname.$tagnameC}) {
$repl=$tags{$tagname.$tagnameC};
push @stak, $text, $repl;
} else {
if ($href ne "") {
push @stak, $text, "</a>";
$useref{$href}=1;
$href="";
} else {
die "$s\n NOT DEFINE REPALCE CLOSE TAG ${tagname}${tagnameC} pos=$pos\n";
}
}
if ($tagname eq "codeclass=\"function\"" && $fn == 1 ) {
$fn=0;
$fname=$text;
}
if ($tagname eq "codeclass=\"literal\"" && $flit == 1 ) {
$fliteral=$text;
}
$text="";
next;
}
my $tmp=substr($s,$pos);
($tagname,$attr) = $tmp =~ m/<(\w+)\s*(.*?)>/g;
my $pp=$pos;
$pos=index($s,'>',$pos)+1;
$href="";
if ($tagname eq "a" ) {($href) = $attr =~ m/href=".*?#(.*?)"/; $attr=""; }
if ($tagname eq "sup" ) {$attr="";}
if ($tagname eq "ul" ) {$attr="";}
if ($tagname eq "pre" ) {$pre=1;}
if ($tagname eq "code" && $attr =~ /literal/ ) {$flit++;}
if ($attr =~ /func_signature/ ) {$fn=1;}
die "$s TAG NAME EMPTY\n" if $tagname eq "" ;
if (exists $tags{$tagname.$attr}) {
$repl=$tags{$tagname.$attr};
if ($href ne "") {
$repl="<a href=\"".$href."\">";
$useref{$href}=1;
#print "$repl\n";
}
push @stak, $text , $repl;
$text="";
} else {
die "$s\n NOT DEFINE REPALCE OPEN TAG ${tagname}${attr}\n";
}
#print substr($s,$pos)."\n =============== tag=$tagname, attr=$attr\n";
push @t, $tagname.$attr;
# print "TAG=${tagname}${attr} pos=$pos prev=$pp\n";
next;
} else {
my $c=substr($s,$pos,1);
if ($pc eq $c && $c eq " " && $pre == 0) {
} else {
if ($pre == 1 && $c eq "\n") {$c="<br>";}
$text.=$c;
$pc=$c;
}
}
$pos++;
}
my $rez;
# dump(@stak);
foreach my $t (@stak) {
next if $t eq "";
$rez.=$t;
}
if (!defined $fname && defined $fliteral) {
$fname=lc($fliteral);
#print "Literal function define $fname\n";
}
if (defined $fname) {
$fname =~ s/&gt;/>/g;
$fname =~ s/&lt;/</g;
$fname =~ s/&amp;/&/g;
$fname=lc($fname);
}
if (defined $fname && exists $function_help{$fname}) {
#print "Dublicate function define $text .Append help\n";
my $v=$function_help{$fname};
$function_help{$fname}=$v."<hr>".$rez;
if ($fname =~ /\||\[/ && length($fname)>3) {
print "Bad name $fname \n";
}
#print "$s\nAppend REZ: $function_help{$fname}\n";
}
if (defined $fname && !exists $function_help{$fname}) {
if ($fname =~ /\||\[/ && length($fname)>3) {
print "Bad name $fname \n";
}
$function_help{$fname}=$rez;
}
# print "$s\nREZ: $rez\n";
@stak=();
#exit;
}

Binary file not shown.