1//
2// Template.cpp
3//
4// Library: JSON
5// Package: JSON
6// Module: Template
7//
8// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
9// and Contributors.
10//
11// SPDX-License-Identifier: BSL-1.0
12//
13
14
15#include "Poco/JSON/Template.h"
16#include "Poco/JSON/TemplateCache.h"
17#include "Poco/JSON/Query.h"
18#include "Poco/File.h"
19#include "Poco/FileStream.h"
20
21
22using Poco::Dynamic::Var;
23
24
25namespace Poco {
26namespace JSON {
27
28
29POCO_IMPLEMENT_EXCEPTION(JSONTemplateException, Exception, "Template Exception")
30
31
32class Part
33{
34public:
35 Part()
36 {
37 }
38
39 virtual ~Part()
40 {
41 }
42
43 virtual void render(const Var& data, std::ostream& out) const = 0;
44
45 typedef std::vector<SharedPtr<Part> > VectorParts;
46};
47
48
49class StringPart: public Part
50{
51public:
52 StringPart(): Part()
53 {
54 }
55
56 StringPart(const std::string& content): Part(), _content(content)
57 {
58 }
59
60 virtual ~StringPart()
61 {
62 }
63
64 void render(const Var& data, std::ostream& out) const
65 {
66 out << _content;
67 }
68
69 void setContent(const std::string& content)
70 {
71 _content = content;
72 }
73
74 inline std::string getContent() const
75 {
76 return _content;
77 }
78
79private:
80 std::string _content;
81};
82
83
84class MultiPart: public Part
85{
86public:
87 MultiPart()
88 {
89 }
90
91 virtual ~MultiPart()
92 {
93 }
94
95 virtual void addPart(Part* part)
96 {
97 _parts.push_back(part);
98 }
99
100 void render(const Var& data, std::ostream& out) const
101 {
102 for (VectorParts::const_iterator it = _parts.begin(); it != _parts.end(); ++it)
103 {
104 (*it)->render(data, out);
105 }
106 }
107
108protected:
109 VectorParts _parts;
110};
111
112
113class EchoPart: public Part
114{
115public:
116 EchoPart(const std::string& query): Part(), _query(query)
117 {
118 }
119
120 virtual ~EchoPart()
121 {
122 }
123
124 void render(const Var& data, std::ostream& out) const
125 {
126 Query query(data);
127 Var value = query.find(_query);
128
129 if (!value.isEmpty())
130 {
131 out << value.convert<std::string>();
132 }
133 }
134
135private:
136 std::string _query;
137};
138
139
140class LogicQuery
141{
142public:
143 LogicQuery(const std::string& query): _queryString(query)
144 {
145 }
146
147 virtual ~LogicQuery()
148 {
149 }
150
151 virtual bool apply(const Var& data) const
152 {
153 bool logic = false;
154
155 Query query(data);
156 Var value = query.find(_queryString);
157
158 if (!value.isEmpty()) // When empty, logic will be false
159 {
160 if (value.isString())
161 // An empty string must result in false, otherwise true
162 // Which is not the case when we convert to bool with Var
163 {
164 std::string s = value.convert<std::string>();
165 logic = !s.empty();
166 }
167 else
168 {
169 // All other values, try to convert to bool
170 // An empty object or array will turn into false
171 // all other values depend on the convert<> in Var
172 logic = value.convert<bool>();
173 }
174 }
175
176 return logic;
177 }
178
179protected:
180 std::string _queryString;
181};
182
183
184class LogicExistQuery: public LogicQuery
185{
186public:
187 LogicExistQuery(const std::string& query): LogicQuery(query)
188 {
189 }
190
191 virtual ~LogicExistQuery()
192 {
193 }
194
195 virtual bool apply(const Var& data) const
196 {
197 Query query(data);
198 Var value = query.find(_queryString);
199
200 return !value.isEmpty();
201 }
202};
203
204
205class LogicElseQuery: public LogicQuery
206{
207public:
208 LogicElseQuery(): LogicQuery("")
209 {
210 }
211
212 virtual ~LogicElseQuery()
213 {
214 }
215
216 virtual bool apply(const Var& data) const
217 {
218 return true;
219 }
220};
221
222
223class LogicPart: public MultiPart
224{
225public:
226 LogicPart(): MultiPart()
227 {
228 }
229
230 virtual ~LogicPart()
231 {
232 }
233
234 void addPart(LogicQuery* query, Part* part)
235 {
236 MultiPart::addPart(part);
237 _queries.push_back(query);
238 }
239
240 void addPart(Part* part)
241 {
242 MultiPart::addPart(part);
243 _queries.push_back(new LogicElseQuery());
244 }
245
246 void render(const Var& data, std::ostream& out) const
247 {
248 int count = 0;
249 for (std::vector<SharedPtr<LogicQuery> >::const_iterator it = _queries.begin(); it != _queries.end(); ++it, ++count)
250 {
251 if ((*it)->apply(data) && _parts.size() > count)
252 {
253 _parts[count]->render(data, out);
254 break;
255 }
256 }
257 }
258
259private:
260 std::vector<SharedPtr<LogicQuery> > _queries;
261};
262
263
264class LoopPart: public MultiPart
265{
266public:
267 LoopPart(const std::string& name, const std::string& query): MultiPart(), _name(name), _query(query)
268 {
269 }
270
271 virtual ~LoopPart()
272 {
273 }
274
275 void render(const Var& data, std::ostream& out) const
276 {
277 Query query(data);
278
279 if (data.type() == typeid(Object::Ptr))
280 {
281 Object::Ptr dataObject = data.extract<Object::Ptr>();
282 Array::Ptr array = query.findArray(_query);
283 if (!array.isNull())
284 {
285 for (int i = 0; i < array->size(); i++)
286 {
287 Var value = array->get(i);
288 dataObject->set(_name, value);
289 MultiPart::render(data, out);
290 }
291 dataObject->remove(_name);
292 }
293 }
294 }
295
296private:
297 std::string _name;
298 std::string _query;
299};
300
301
302class IncludePart: public Part
303{
304public:
305
306 IncludePart(const Path& parentPath, const Path& path):
307 Part(),
308 _path(path)
309 {
310 // When the path is relative, try to make it absolute based
311 // on the path of the parent template. When the file doesn't
312 // exist, we keep it relative and hope that the cache can
313 // resolve it.
314 if (_path.isRelative())
315 {
316 Path templatePath(parentPath, _path);
317 File templateFile(templatePath);
318 if (templateFile.exists())
319 {
320 _path = templatePath;
321 }
322 }
323 }
324
325 virtual ~IncludePart()
326 {
327 }
328
329 void render(const Var& data, std::ostream& out) const
330 {
331 TemplateCache* cache = TemplateCache::instance();
332 if (cache == 0)
333 {
334 Template tpl(_path);
335 tpl.parse();
336 tpl.render(data, out);
337 }
338 else
339 {
340 Template::Ptr tpl = cache->getTemplate(_path);
341 tpl->render(data, out);
342 }
343 }
344
345private:
346 Path _path;
347};
348
349
350Template::Template(const Path& templatePath):
351 _parts(0),
352 _currentPart(0),
353 _templatePath(templatePath)
354{
355}
356
357
358Template::Template():
359 _parts(0),
360 _currentPart(0)
361{
362}
363
364
365Template::~Template()
366{
367 delete _parts;
368}
369
370
371void Template::parse()
372{
373 File file(_templatePath);
374 if (file.exists())
375 {
376 FileInputStream fis(_templatePath.toString());
377 parse(fis);
378 }
379}
380
381
382void Template::parse(std::istream& in)
383{
384 _parseTime.update();
385
386 _parts = new MultiPart;
387 _currentPart = _parts;
388
389 while (in.good())
390 {
391 std::string text = readText(in); // Try to read text first
392 if (text.length() > 0)
393 {
394 _currentPart->addPart(new StringPart(text));
395 }
396
397 if (in.bad())
398 break; // Nothing to do anymore
399
400 std::string command = readTemplateCommand(in); // Try to read a template command
401 if (command.empty())
402 {
403 break;
404 }
405
406 readWhiteSpace(in);
407
408 if (command.compare("echo") == 0)
409 {
410 std::string query = readQuery(in);
411 if (query.empty())
412 {
413 throw JSONTemplateException("Missing query in <? echo ?>");
414 }
415 _currentPart->addPart(new EchoPart(query));
416 }
417 else if (command.compare("for") == 0)
418 {
419 std::string loopVariable = readWord(in);
420 if (loopVariable.empty())
421 {
422 throw JSONTemplateException("Missing variable in <? for ?> command");
423 }
424 readWhiteSpace(in);
425
426 std::string query = readQuery(in);
427 if (query.empty())
428 {
429 throw JSONTemplateException("Missing query in <? for ?> command");
430 }
431
432 _partStack.push(_currentPart);
433 LoopPart* part = new LoopPart(loopVariable, query);
434 _partStack.push(part);
435 _currentPart->addPart(part);
436 _currentPart = part;
437 }
438 else if (command.compare("else") == 0)
439 {
440 if (_partStack.size() == 0)
441 {
442 throw JSONTemplateException("Unexpected <? else ?> found");
443 }
444 _currentPart = _partStack.top();
445 LogicPart* lp = dynamic_cast<LogicPart*>(_currentPart);
446 if (lp == 0)
447 {
448 throw JSONTemplateException("Missing <? if ?> or <? ifexist ?> for <? else ?>");
449 }
450 MultiPart* part = new MultiPart();
451 lp->addPart(part);
452 _currentPart = part;
453 }
454 else if (command.compare("elsif") == 0 || command.compare("elif") == 0)
455 {
456 std::string query = readQuery(in);
457 if (query.empty())
458 {
459 throw JSONTemplateException("Missing query in <? " + command + " ?>");
460 }
461
462 if (_partStack.size() == 0)
463 {
464 throw JSONTemplateException("Unexpected <? elsif / elif ?> found");
465 }
466
467 _currentPart = _partStack.top();
468 LogicPart* lp = dynamic_cast<LogicPart*>(_currentPart);
469 if (lp == 0)
470 {
471 throw JSONTemplateException("Missing <? if ?> or <? ifexist ?> for <? elsif / elif ?>");
472 }
473 MultiPart* part = new MultiPart();
474 lp->addPart(new LogicQuery(query), part);
475 _currentPart = part;
476 }
477 else if (command.compare("endfor") == 0)
478 {
479 if (_partStack.size() < 2)
480 {
481 throw JSONTemplateException("Unexpected <? endfor ?> found");
482 }
483 MultiPart* loopPart = _partStack.top();
484 LoopPart* lp = dynamic_cast<LoopPart*>(loopPart);
485 if (lp == 0)
486 {
487 throw JSONTemplateException("Missing <? for ?> command");
488 }
489 _partStack.pop();
490 _currentPart = _partStack.top();
491 _partStack.pop();
492 }
493 else if (command.compare("endif") == 0)
494 {
495 if (_partStack.size() < 2)
496 {
497 throw JSONTemplateException("Unexpected <? endif ?> found");
498 }
499
500 _currentPart = _partStack.top();
501 LogicPart* lp = dynamic_cast<LogicPart*>(_currentPart);
502 if (lp == 0)
503 {
504 throw JSONTemplateException("Missing <? if ?> or <? ifexist ?> for <? endif ?>");
505 }
506
507 _partStack.pop();
508 _currentPart = _partStack.top();
509 _partStack.pop();
510 }
511 else if (command.compare("if") == 0 || command.compare("ifexist") == 0)
512 {
513 std::string query = readQuery(in);
514 if (query.empty())
515 {
516 throw JSONTemplateException("Missing query in <? " + command + " ?>");
517 }
518 _partStack.push(_currentPart);
519 LogicPart* lp = new LogicPart();
520 _partStack.push(lp);
521 _currentPart->addPart(lp);
522 _currentPart = new MultiPart();
523 if (command.compare("ifexist") == 0)
524 {
525 lp->addPart(new LogicExistQuery(query), _currentPart);
526 }
527 else
528 {
529 lp->addPart(new LogicQuery(query), _currentPart);
530 }
531 }
532 else if (command.compare("include") == 0)
533 {
534 readWhiteSpace(in);
535 std::string filename = readString(in);
536 if (filename.empty())
537 {
538 throw JSONTemplateException("Missing filename in <? include ?>");
539 }
540 else
541 {
542 Path resolvePath(_templatePath);
543 resolvePath.makeParent();
544 _currentPart->addPart(new IncludePart(resolvePath, filename));
545 }
546 }
547 else
548 {
549 throw JSONTemplateException("Unknown command " + command);
550 }
551
552 readWhiteSpace(in);
553
554 int c = in.get();
555 if (c == '?' && in.peek() == '>')
556 {
557 in.get(); // forget '>'
558
559 if (command.compare("echo") != 0)
560 {
561 if (in.peek() == '\r')
562 {
563 in.get();
564 }
565 if (in.peek() == '\n')
566 {
567 in.get();
568 }
569 }
570 }
571 else
572 {
573 throw JSONTemplateException("Missing ?>");
574 }
575 }
576}
577
578
579std::string Template::readText(std::istream& in)
580{
581 std::string text;
582 int c = in.get();
583 while (c != -1)
584 {
585 if (c == '<')
586 {
587 if (in.peek() == '?')
588 {
589 in.get(); // forget '?'
590 break;
591 }
592 }
593 text += c;
594
595 c = in.get();
596 }
597 return text;
598}
599
600
601std::string Template::readTemplateCommand(std::istream& in)
602{
603 std::string command;
604
605 readWhiteSpace(in);
606
607 int c = in.get();
608 while (c != -1)
609 {
610 if (Ascii::isSpace(c))
611 break;
612
613 if (c == '?' && in.peek() == '>')
614 {
615 in.putback(c);
616 break;
617 }
618
619 if (c == '=' && command.length() == 0)
620 {
621 command = "echo";
622 break;
623 }
624
625 command += c;
626
627 c = in.get();
628 }
629
630 return command;
631}
632
633
634std::string Template::readWord(std::istream& in)
635{
636 std::string word;
637 int c;
638 while ((c = in.peek()) != -1 && !Ascii::isSpace(c))
639 {
640 in.get();
641 word += c;
642 }
643 return word;
644}
645
646
647std::string Template::readQuery(std::istream& in)
648{
649 std::string word;
650 int c;
651 while ((c = in.get()) != -1)
652 {
653 if (c == '?' && in.peek() == '>')
654 {
655 in.putback(c);
656 break;
657 }
658
659 if (Ascii::isSpace(c))
660 {
661 break;
662 }
663 word += c;
664 }
665 return word;
666}
667
668
669void Template::readWhiteSpace(std::istream& in)
670{
671 int c;
672 while ((c = in.peek()) != -1 && Ascii::isSpace(c))
673 {
674 in.get();
675 }
676}
677
678
679std::string Template::readString(std::istream& in)
680{
681 std::string str;
682
683 int c = in.get();
684 if (c == '"')
685 {
686 while ((c = in.get()) != -1 && c != '"')
687 {
688 str += c;
689 }
690 }
691 return str;
692}
693
694
695void Template::render(const Var& data, std::ostream& out) const
696{
697 _parts->render(data, out);
698}
699
700
701} } // namespace Poco::JSON
702