1 /*  $Id: page.cpp 626608 2021-03-02 14:23:58Z ivanov $
2  * ===========================================================================
3  *
4  *                            PUBLIC DOMAIN NOTICE
5  *               National Center for Biotechnology Information
6  *
7  *  This software/database is a "United States Government Work" under the
8  *  terms of the United States Copyright Act.  It was written as part of
9  *  the author's official duties as a United States Government employee and
10  *  thus cannot be copyrighted.  This software/database is freely available
11  *  to the public for use. The National Library of Medicine and the U.S.
12  *  Government have not placed any restriction on its use or reproduction.
13  *
14  *  Although all reasonable efforts have been taken to ensure the accuracy
15  *  and reliability of the software and data, the NLM and the U.S.
16  *  Government do not and cannot warrant the performance or results that
17  *  may be obtained by using this software or data. The NLM and the U.S.
18  *  Government disclaim all warranties, express or implied, including
19  *  warranties of performance, merchantability or fitness for any particular
20  *  purpose.
21  *
22  *  Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Author:  Lewis Geer
27  *
28  */
29 
30 #include <ncbi_pch.hpp>
31 #include <corelib/ncbiutil.hpp>
32 #include <corelib/ncbi_safe_static.hpp>
33 #include <corelib/request_ctx.hpp>
34 #include <corelib/ncbi_strings.h>
35 #include <html/components.hpp>
36 #include <html/page.hpp>
37 
38 #include <errno.h>
39 
40 BEGIN_NCBI_SCOPE
41 
42 
43 // The buffer size for reading from stream.
44 const SIZE_TYPE kBufferSize = 4096;
45 
46 extern const char* kTagStart;
47 extern const char* kTagEnd;
48 // Tag start in the end of block definition (see page templates)
49 const char* kTagStartEnd = "</@";
50 
51 // Template file caching (disabled by default)
52 CHTMLPage::ECacheTemplateFiles CHTMLPage::sm_CacheTemplateFiles = CHTMLPage::eCTF_Disable;
53 typedef map<string, string*> TTemplateCache;
54 static CSafeStatic<TTemplateCache> s_TemplateCache;
55 
56 
GetValue(const string & name) const57 const string& CPageStat::GetValue(const string& name) const
58 {
59     TData::const_iterator it = m_Data.find(name);
60     return it == m_Data.end() ? kEmptyStr : it->second;
61 }
62 
63 
SetValue(const string & name,const string & value)64 void CPageStat::SetValue(const string& name, const string& value)
65 {
66     if ( !value.empty() ) {
67         m_Data[name] = value;
68     }
69     else {
70         TData::iterator it = m_Data.find(name);
71         if (it != m_Data.end()) {
72             m_Data.erase(it);
73         }
74     }
75 }
76 
77 
78 class CHTMLPageStat : public CNCBINode
79 {
80     typedef CNCBINode CParent;
81 public:
82     CHTMLPageStat(CHTMLBasicPage& page);
83     ~CHTMLPageStat(void);
84 
85     virtual CNcbiOstream& PrintBegin(CNcbiOstream& out, TMode mode);
86 
87 private:
88     const CHTMLBasicPage& m_Page;
89 };
90 
91 
CHTMLPageStat(CHTMLBasicPage & page)92 CHTMLPageStat::CHTMLPageStat(CHTMLBasicPage& page)
93     : CNCBINode("ncbipagestat"),
94       m_Page(page)
95 {
96     return;
97 }
98 
99 
~CHTMLPageStat(void)100 CHTMLPageStat::~CHTMLPageStat(void)
101 {
102     return;
103 }
104 
105 
PrintBegin(CNcbiOstream & out,TMode mode)106 CNcbiOstream& CHTMLPageStat::PrintBegin(CNcbiOstream& out, TMode mode)
107 {
108     const CPageStat::TData& stat = m_Page.GetPageStat().GetData();
109     if ( stat.empty() ) {
110         return out;
111     }
112     bool phid_present = false;
113     string phid = CDiagContext::GetRequestContext().GetHitID();
114     ITERATE(CPageStat::TData, it, stat) {
115         if ( NStr::EqualNocase(it->first,
116             g_GetNcbiString(eNcbiStrings_PHID)) ) {
117             phid_present = true;
118         }
119         CHTML_meta meta(CHTML_meta::eName, it->first, it->second);
120         meta.PrintBegin(out, mode);
121         out << endl;
122     }
123     if ( !phid_present  &&  !phid.empty() ) {
124         CHTML_meta meta(CHTML_meta::eName, g_GetNcbiString(eNcbiStrings_PHID),
125             phid);
126         meta.PrintBegin(out, mode);
127         out << endl;
128     }
129     return out;
130 }
131 
132 
133 // CHTMLBasicPage
134 
CHTMLBasicPage(void)135 CHTMLBasicPage::CHTMLBasicPage(void)
136     : CParent("basicpage"),
137       m_CgiApplication(0),
138       m_Style(0)
139 {
140     AddTagMap("NCBI_PAGE_STAT", new CHTMLPageStat(*this));
141     return;
142 }
143 
144 
CHTMLBasicPage(CCgiApplication * application,int style)145 CHTMLBasicPage::CHTMLBasicPage(CCgiApplication* application, int style)
146     : m_CgiApplication(application),
147       m_Style(style),
148       m_PrintMode(eHTML)
149 {
150     AddTagMap("NCBI_PAGE_STAT", new CHTMLPageStat(*this));
151     return;
152 }
153 
154 
~CHTMLBasicPage(void)155 CHTMLBasicPage::~CHTMLBasicPage(void)
156 {
157     for (TTagMap::iterator i = m_TagMap.begin(); i != m_TagMap.end(); ++i) {
158         delete i->second;
159     }
160 }
161 
162 
SetApplication(CCgiApplication * App)163 void CHTMLBasicPage::SetApplication(CCgiApplication* App)
164 {
165     m_CgiApplication = App;
166 }
167 
168 
SetStyle(int style)169 void CHTMLBasicPage::SetStyle(int style)
170 {
171     m_Style = style;
172 }
173 
174 
MapTag(const string & name)175 CNCBINode* CHTMLBasicPage::MapTag(const string& name)
176 {
177     map<string, BaseTagMapper*>::iterator i = m_TagMap.find(name);
178     if ( i != m_TagMap.end() ) {
179         return (i->second)->MapTag(this, name);
180     }
181     return CParent::MapTag(name);
182 }
183 
184 
AddTagMap(const string & name,CNCBINode * node)185 void CHTMLBasicPage::AddTagMap(const string& name, CNCBINode* node)
186 {
187     AddTagMap(name, CreateTagMapper(node));
188 }
189 
190 
AddTagMap(const string & name,BaseTagMapper * mapper)191 void CHTMLBasicPage::AddTagMap(const string& name, BaseTagMapper* mapper)
192 {
193     delete m_TagMap[name];
194     m_TagMap[name] = mapper;
195 }
196 
197 
198 // CHTMLPage
199 
CHTMLPage(const string & title)200 CHTMLPage::CHTMLPage(const string& title)
201     : m_Title(title)
202 {
203     Init();
204 }
205 
206 
CHTMLPage(const string & title,const string & template_file)207 CHTMLPage::CHTMLPage(const string& title, const string& template_file)
208     : m_Title(title)
209 {
210     Init();
211     SetTemplateFile(template_file);
212 }
213 
214 
CHTMLPage(const string & title,istream & template_stream)215 CHTMLPage::CHTMLPage(const string& title, istream& template_stream)
216     : m_Title(title)
217 {
218     Init();
219     SetTemplateStream(template_stream);
220 }
221 
222 
CHTMLPage(const string &,const void * template_buffer,SIZE_TYPE size)223 CHTMLPage::CHTMLPage(const string& /*title*/,
224                      const void* template_buffer, SIZE_TYPE size)
225 {
226     Init();
227     SetTemplateBuffer(template_buffer, size);
228 }
229 
230 
CHTMLPage(CCgiApplication * application,int style,const string & title,const string & template_file)231 CHTMLPage::CHTMLPage(CCgiApplication* application, int style,
232                      const string& title, const string& template_file)
233     : CParent(application, style),
234       m_Title(title)
235 {
236     Init();
237     SetTemplateFile(template_file);
238 }
239 
240 
Init(void)241 void CHTMLPage::Init(void)
242 {
243     // Generate internal page name
244     GeneratePageInternalName();
245 
246     // Template sources
247     m_TemplateFile   = kEmptyStr;
248     m_TemplateStream = 0;
249     m_TemplateBuffer = 0;
250     m_TemplateSize   = 0;
251 
252     AddTagMap("TITLE", CreateTagMapper(this, &CHTMLPage::CreateTitle));
253     AddTagMap("VIEW",  CreateTagMapper(this, &CHTMLPage::CreateView));
254 }
255 
256 
CreateSubNodes(void)257 void CHTMLPage::CreateSubNodes(void)
258 {
259     bool create_on_print = (m_TemplateFile.empty()  || sm_CacheTemplateFiles == eCTF_Disable);
260     if ( !create_on_print ) {
261         AppendChild(CreateTemplate());
262     }
263     // Otherwise, create template while printing to avoid
264     // latency on large files
265 }
266 
267 
CreateTitle(void)268 CNCBINode* CHTMLPage::CreateTitle(void)
269 {
270     if ( GetStyle() & fNoTITLE )
271         return 0;
272 
273     return new CHTMLText(m_Title);
274 }
275 
276 
CreateView(void)277 CNCBINode* CHTMLPage::CreateView(void)
278 {
279     return 0;
280 }
281 
282 
AddTagMap(const string & name,CNCBINode * node)283 void CHTMLPage::AddTagMap(const string& name, CNCBINode* node)
284 {
285     CParent::AddTagMap(name, node);
286 }
287 
288 
AddTagMap(const string & name,BaseTagMapper * mapper)289 void CHTMLPage::AddTagMap(const string& name, BaseTagMapper* mapper)
290 {
291     CParent::AddTagMap(name,mapper);
292 }
293 
294 
PrintChildren(CNcbiOstream & out,TMode mode)295 CNcbiOstream& CHTMLPage::PrintChildren(CNcbiOstream& out, TMode mode)
296 {
297     if (HaveChildren()) {
298         return CParent::PrintChildren(out, mode);
299     } else {
300         m_PrintMode = mode;
301         AppendChild(CreateTemplate(&out, mode));
302         return out;
303     }
304 }
305 
306 
CreateTemplate(CNcbiOstream * out,CNCBINode::TMode mode)307 CNCBINode* CHTMLPage::CreateTemplate(CNcbiOstream* out, CNCBINode::TMode mode)
308 {
309     string  str;
310     string* pstr = &str;
311 
312     TTemplateCache& cache = s_TemplateCache.Get();
313 
314     // File
315     if ( !m_TemplateFile.empty() ) {
316         if ( sm_CacheTemplateFiles == eCTF_Enable ) {
317             TTemplateCache::const_iterator i
318                 = cache.find(m_TemplateFile);
319             if ( i != cache.end() ) {
320                 pstr = i->second;
321             } else {
322                 pstr = new string();
323                 CNcbiIfstream is(m_TemplateFile.c_str());
324                 x_LoadTemplate(is, *pstr);
325                 cache[m_TemplateFile] = pstr;
326             }
327         } else {
328             CNcbiIfstream is(m_TemplateFile.c_str());
329             if ( out ) {
330                 return x_PrintTemplate(is, out, mode);
331             }
332             x_LoadTemplate(is, str);
333         }
334 
335     // Stream
336     } else if ( m_TemplateStream ) {
337         if ( out ) {
338             return x_PrintTemplate(*m_TemplateStream, out, mode);
339         }
340         x_LoadTemplate(*m_TemplateStream, str);
341 
342     // Buffer
343     } else if ( m_TemplateBuffer ) {
344         str.assign((char*)m_TemplateBuffer, m_TemplateSize);
345 
346     // Otherwise
347     } else {
348         return new CHTMLText(kEmptyStr);
349     }
350 
351     // Print and return node
352     {{
353         unique_ptr<CHTMLText> node(new CHTMLText(*pstr));
354         if ( out ) {
355             node->Print(*out, mode);
356         }
357         return node.release();
358     }}
359 }
360 
361 
x_LoadTemplate(CNcbiIstream & is,string & str)362 void CHTMLPage::x_LoadTemplate(CNcbiIstream& is, string& str)
363 {
364     if ( !is.good() ) {
365         NCBI_THROW(CHTMLException, eTemplateAccess,
366                    "CHTMLPage::x_LoadTemplate(): failed to open template");
367     }
368 
369     char buf[kBufferSize];
370 
371     // If loading template from the file, get its size first
372     if ( m_TemplateFile.size() ) {
373         Int8 size = CFile(m_TemplateFile).GetLength();
374         if (size < 0) {
375             NCBI_THROW(CHTMLException, eTemplateAccess,
376                        "CHTMLPage::x_LoadTemplate(): failed to "  \
377                        "open template file '" + m_TemplateFile + "'");
378         }
379         if ((Uint8)size >= numeric_limits<size_t>::max()) {
380             NCBI_THROW(CHTMLException, eTemplateTooBig,
381                        "CHTMLPage: input template " + m_TemplateFile
382                        + " too big to handle");
383         }
384         m_TemplateSize = (SIZE_TYPE)size;
385     }
386     // Reserve space
387     if ( m_TemplateSize ) {
388         str.reserve(m_TemplateSize);
389     }
390     while ( is ) {
391         is.read(buf, sizeof(buf));
392         if (m_TemplateSize == 0  &&  is.gcount() > 0
393             &&  str.size() == str.capacity()) {
394             // We don't know how big string will need to be,
395             // so we grow it exponentially.
396             str.reserve(str.size() + max((SIZE_TYPE)is.gcount(),
397                         str.size() / 2));
398         }
399         str.append(buf, (SIZE_TYPE)is.gcount());
400     }
401 
402     if ( !is.eof() ) {
403         NCBI_THROW(CHTMLException, eTemplateAccess,
404                    "CHTMLPage::x_LoadTemplate(): error reading template");
405     }
406 }
407 
408 
x_PrintTemplate(CNcbiIstream & is,CNcbiOstream * out,CNCBINode::TMode mode)409 CNCBINode* CHTMLPage::x_PrintTemplate(CNcbiIstream& is, CNcbiOstream* out,
410                                       CNCBINode::TMode mode)
411 {
412     if ( !is.good() ) {
413         NCBI_THROW(CHTMLException, eTemplateAccess,
414                    "CHTMLPage::x_PrintTemplate(): failed to open template");
415     }
416     if ( !out ) {
417         NCBI_THROW(CHTMLException, eNullPtr,
418                    "CHTMLPage::x_PrintTemplate(): " \
419                    "output stream must be specified");
420     }
421 
422     string str;
423     char   buf[kBufferSize];
424     unique_ptr<CNCBINode> node(new CNCBINode);
425 
426     while (is) {
427         is.read(buf, sizeof(buf));
428         str.append(buf, (SIZE_TYPE)is.gcount());
429         SIZE_TYPE pos = str.rfind('\n');
430         if (pos != NPOS) {
431             ++pos;
432             CHTMLText* child = new CHTMLText(str.substr(0, pos));
433             child->Print(*out, mode);
434             node->AppendChild(child);
435             str.erase(0, pos);
436         }
437     }
438     if ( !str.empty() ) {
439         CHTMLText* child = new CHTMLText(str);
440         child->Print(*out, mode);
441         node->AppendChild(child);
442     }
443 
444     if ( !is.eof() ) {
445         NCBI_THROW(CHTMLException, eTemplateAccess,
446                     "CHTMLPage::x_PrintTemplate(): error reading template");
447     }
448 
449     return node.release();
450 }
451 
452 
CacheTemplateFiles(ECacheTemplateFiles caching)453 void CHTMLPage::CacheTemplateFiles(ECacheTemplateFiles caching)
454 {
455     sm_CacheTemplateFiles = caching;
456 }
457 
458 
SetTemplateFile(const string & template_file)459 void CHTMLPage::SetTemplateFile(const string& template_file)
460 {
461     m_TemplateFile   = template_file;
462     m_TemplateStream = 0;
463     m_TemplateBuffer = 0;
464     m_TemplateSize   = 0;
465     GeneratePageInternalName(template_file);
466 }
467 
468 
s_Find(const string & s,const char * target,SIZE_TYPE start=0)469 static SIZE_TYPE s_Find(const string& s, const char* target,
470                         SIZE_TYPE start = 0)
471 {
472     // Return s.find(target);
473     // Some implementations of string::find call memcmp at every
474     // possible position, which is way too slow.
475     if ( start >= s.size() ) {
476         return NPOS;
477     }
478     const char* cstr = s.c_str();
479     const char* p    = strstr(cstr + start, target);
480     return p ? p - cstr : NPOS;
481 }
482 
x_ApplyFilters(TTemplateLibFilter * filter,const char * buffer)483 bool CHTMLPage::x_ApplyFilters(TTemplateLibFilter* filter, const char* buffer)
484 {
485     bool template_applicable = true;
486 
487     while (*buffer != '\0') {
488         while (isspace(*buffer))
489             ++buffer;
490 
491         const char* id_begin = buffer;
492 
493         for (; *buffer != '\0'; ++buffer)
494             if (*buffer == '(' || *buffer == '<' || *buffer == '{')
495                 break;
496 
497         if (id_begin == buffer || *buffer == '\0')
498             break;
499 
500         string id(id_begin, buffer - id_begin);
501 
502         char bracket_stack[sizeof(long)];
503         char* bracket_stack_pos = bracket_stack + sizeof(bracket_stack) - 1;
504 
505         *bracket_stack_pos = '\0';
506 
507         for (;;) {
508             char closing_bracket;
509 
510             if (*buffer == '(')
511                 closing_bracket = ')';
512             else if (*buffer == '<')
513                 closing_bracket = '>';
514             else if (*buffer == '{')
515                 closing_bracket = '}';
516             else
517                 break;
518 
519             if (bracket_stack_pos == bracket_stack) {
520                 NCBI_THROW(CHTMLException, eUnknown,
521                     "Bracket nesting is too deep");
522             }
523 
524             *--bracket_stack_pos = closing_bracket;
525             ++buffer;
526         }
527 
528         const char* pattern_end;
529 
530         if ((pattern_end = strstr(buffer, bracket_stack_pos)) == NULL) {
531             NCBI_THROW(CHTMLException, eUnknown,
532                     "Unterminated filter expression");
533         }
534 
535         if (template_applicable && (filter == NULL ||
536                 !filter->TestAttribute(id, string(buffer, pattern_end))))
537             template_applicable = false;
538 
539         buffer = pattern_end + (bracket_stack +
540             sizeof(bracket_stack) - 1 - bracket_stack_pos);
541     }
542 
543     return template_applicable;
544 }
545 
x_LoadTemplateLib(CNcbiIstream & istrm,SIZE_TYPE size,ETemplateIncludes includes,const string & file_name,TTemplateLibFilter * filter)546 void CHTMLPage::x_LoadTemplateLib(CNcbiIstream& istrm, SIZE_TYPE size,
547                                   ETemplateIncludes includes,
548                                   const string& file_name /* = kEmptyStr */,
549                                   TTemplateLibFilter* filter)
550 {
551     string  template_buf("\n");
552     string* pstr      = &template_buf;
553     bool    caching   = false;
554     bool    need_read = true;
555 
556     AutoPtr<CNcbiIstream> is(&istrm, eNoOwnership);
557     TTemplateCache& cache = s_TemplateCache.Get();
558 
559     if ( !file_name.empty()  &&  sm_CacheTemplateFiles == eCTF_Enable ) {
560         TTemplateCache::const_iterator i = cache.find(file_name);
561         if ( i != cache.end() ) {
562             pstr = i->second;
563             need_read = false;
564         } else {
565             pstr = new string();
566             caching = true;
567         }
568     }
569 
570     // Load template in memory all-in-all
571     if ( need_read ) {
572         // Open and check file, if this is a file template
573         if ( !file_name.empty() ) {
574             Int8 x_size = CFile(file_name).GetLength();
575             if (x_size == 0) {
576                 return;
577             } else if (x_size < 0) {
578                 NCBI_THROW(CHTMLException, eTemplateAccess,
579                            "CHTMLPage::x_LoadTemplateLib(): failed to "  \
580                            "open template file '" + file_name  + "'");
581             } else if ((Uint8)x_size >= numeric_limits<size_t>::max()) {
582                 NCBI_THROW(CHTMLException, eTemplateTooBig,
583                            "CHTMLPage::x_LoadTemplateLib(): template " \
584                            "file '" + file_name +
585                            "' is too big to handle");
586             }
587             is.reset(new CNcbiIfstream(file_name.c_str()), eTakeOwnership);
588             size = (SIZE_TYPE)x_size;
589         }
590 
591         // Reserve space
592         if ( size ) {
593             pstr->reserve(size);
594         }
595         if (includes == eAllowIncludes) {
596             // Read line by line and parse it for #includes
597             string s;
598             static const char*     kInclude = "#include ";
599             static const SIZE_TYPE kIncludeLen = strlen(kInclude);
600 
601             for (int i = 1;  NcbiGetline(*is, s, "\r\n");  ++i) {
602 
603                 if ( NStr::StartsWith(s, kInclude) ) {
604                     SIZE_TYPE pos = kIncludeLen;
605                     SIZE_TYPE len = s.length();
606                     while (pos < len  && isspace((unsigned char)s[pos])) {
607                         pos++;
608                     }
609                     bool error = false;
610                     if (pos < len  &&  s[pos] == '\"') {
611                         pos++;
612                         SIZE_TYPE pos_end = s.find("\"", pos);
613                         if (pos_end == NPOS) {
614                             error = true;
615                         } else {
616                             string fname = s.substr(pos, pos_end-pos);
617                             LoadTemplateLibFile(fname);
618                         }
619                     } else {
620                         error = true;
621                     }
622                     if ( error ) {
623                         NCBI_THROW(CHTMLException, eTemplateAccess,
624                                    "CHTMLPage::x_LoadTemplateLib(): " \
625                                    "incorrect #include syntax, file '" +
626                                    file_name + "', line " +
627                                    NStr::IntToString(i));
628                     }
629 
630                 } else {  // General line
631 
632                     if (pstr->size() == pstr->capacity() &&
633                         s.length() > 0) {
634                         // We don't know how big str will need to be,
635                         // so we grow it exponentially.
636                         pstr->reserve(pstr->size() +
637                                       max((SIZE_TYPE)is->gcount(),
638                                       pstr->size() / 2));
639                     }
640                     pstr->append(s + "\n");
641                 }
642             }
643         } else {
644             // Use faster block read
645             char buf[kBufferSize];
646             while (is) {
647                 is->read(buf, sizeof(buf));
648                 if (pstr->size() == pstr->capacity()  &&
649                     is->gcount() > 0) {
650                     // We don't know how big str will need to be,
651                     // so we grow it exponentially.
652                     pstr->reserve(pstr->size() +
653                                   max((SIZE_TYPE)is->gcount(),
654                                   pstr->size() / 2));
655                 }
656                 pstr->append(buf, (SIZE_TYPE)is->gcount());
657             }
658         }
659         if ( !is->eof() ) {
660             NCBI_THROW(CHTMLException, eTemplateAccess,
661                        "CHTMLPage::x_LoadTemplateLib(): " \
662                        "error reading template");
663         }
664     }
665 
666     // Cache template lib
667     if ( caching ) {
668         cache[file_name] = pstr;
669     }
670 
671     // Parse template
672     // Note: never change pstr here!
673 
674     const string kTagStartBOL(string("\n") + kTagStart);
675     SIZE_TYPE ts_size   = kTagStartBOL.length();
676     SIZE_TYPE te_size   = strlen(kTagEnd);
677     SIZE_TYPE tse_size  = strlen(kTagStartEnd);
678     SIZE_TYPE tag_start = s_Find(*pstr, kTagStartBOL.c_str());
679 
680     while ( tag_start != NPOS ) {
681 
682         // Get name
683         string name;
684         SIZE_TYPE name_start = tag_start + ts_size;
685         SIZE_TYPE name_end   = s_Find(*pstr, kTagEnd, name_start);
686         if ( name_end == NPOS ) {
687             // Tag not closed
688             NCBI_THROW(CHTMLException, eTextUnclosedTag,
689                 "opening tag \"" + name + "\" not closed, " \
690                 "stream pos = " + NStr::NumericToString(tag_start));
691         }
692         if (name_end != name_start) {
693             // Tag found
694             name = pstr->substr(name_start, name_end - name_start);
695         }
696         bool template_applicable = true;
697         string::size_type space_pos;
698         if ((space_pos = name.find_first_of(" \t")) != string::npos) {
699             template_applicable =
700                 x_ApplyFilters(filter, name.c_str() + space_pos + 1);
701             name.erase(space_pos);
702         }
703         SIZE_TYPE tag_end = name_end + te_size;
704 
705         // Find close tags for "name"
706         string close_str = kTagStartEnd;
707         if ( !name.empty() ) {
708             close_str += name + kTagEnd;
709         }
710         SIZE_TYPE last = s_Find(*pstr, close_str.c_str(), tag_end);
711         if ( last == NPOS ) {
712             // Tag not closed
713             NCBI_THROW(CHTMLException, eTextUnclosedTag,
714                 "closing tag \"" + name + "\" not closed, " \
715                 "stream pos = " + NStr::NumericToString(tag_end));
716         }
717         if ( name.empty() ) {
718             tag_start = s_Find(*pstr, kTagStartBOL.c_str(),
719                                last + tse_size);
720             continue;
721         }
722 
723         // Is it a multi-line template? Remove redundant line breaks.
724         SIZE_TYPE pos = pstr->find_first_not_of(" ", tag_end);
725         if (pos != NPOS  &&  (*pstr)[pos] == '\n') {
726             tag_end = pos + 1;
727         }
728         pos = pstr->find_first_not_of(" ", last - 1);
729         if (pos != NPOS  &&  (*pstr)[pos] == '\n') {
730             last = pos;
731         }
732 
733         // Get sub-template
734         string subtemplate = pstr->substr(tag_end, last - tag_end);
735 
736         // Add sub-template resolver
737         if (template_applicable)
738             AddTagMap(name, CreateTagMapper(new CHTMLText(subtemplate)));
739 
740         // Find next
741         tag_start = s_Find(*pstr, kTagStartBOL.c_str(),
742                            last + te_size + name.size() + tse_size);
743 
744     }
745 }
746 
747 
LoadTemplateLibFile(const string & template_file,TTemplateLibFilter * filter)748 void CHTMLPage::LoadTemplateLibFile(const string& template_file,
749                                     TTemplateLibFilter* filter)
750 {
751     // We will open file in x_LoadTemplateLib just before reading from it.
752     // This allow to minimize stat() calls when template caching is enabled.
753     CNcbiIfstream is;
754     x_LoadTemplateLib(is, 0 /* size - determine later */,
755                       eAllowIncludes, template_file, filter);
756 }
757 
758 
759 END_NCBI_SCOPE
760