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