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 | |
22 | using Poco::Dynamic::Var; |
23 | |
24 | |
25 | namespace Poco { |
26 | namespace JSON { |
27 | |
28 | |
29 | POCO_IMPLEMENT_EXCEPTION(JSONTemplateException, Exception, "Template Exception" ) |
30 | |
31 | |
32 | class Part |
33 | { |
34 | public: |
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 | |
49 | class StringPart: public Part |
50 | { |
51 | public: |
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 | |
79 | private: |
80 | std::string _content; |
81 | }; |
82 | |
83 | |
84 | class MultiPart: public Part |
85 | { |
86 | public: |
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 | |
108 | protected: |
109 | VectorParts _parts; |
110 | }; |
111 | |
112 | |
113 | class EchoPart: public Part |
114 | { |
115 | public: |
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 | |
135 | private: |
136 | std::string _query; |
137 | }; |
138 | |
139 | |
140 | class LogicQuery |
141 | { |
142 | public: |
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 | |
179 | protected: |
180 | std::string _queryString; |
181 | }; |
182 | |
183 | |
184 | class LogicExistQuery: public LogicQuery |
185 | { |
186 | public: |
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 | |
205 | class LogicElseQuery: public LogicQuery |
206 | { |
207 | public: |
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 | |
223 | class LogicPart: public MultiPart |
224 | { |
225 | public: |
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 | |
259 | private: |
260 | std::vector<SharedPtr<LogicQuery> > _queries; |
261 | }; |
262 | |
263 | |
264 | class LoopPart: public MultiPart |
265 | { |
266 | public: |
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 | |
296 | private: |
297 | std::string _name; |
298 | std::string _query; |
299 | }; |
300 | |
301 | |
302 | class IncludePart: public Part |
303 | { |
304 | public: |
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 | |
345 | private: |
346 | Path _path; |
347 | }; |
348 | |
349 | |
350 | Template::Template(const Path& templatePath): |
351 | _parts(0), |
352 | _currentPart(0), |
353 | _templatePath(templatePath) |
354 | { |
355 | } |
356 | |
357 | |
358 | Template::Template(): |
359 | _parts(0), |
360 | _currentPart(0) |
361 | { |
362 | } |
363 | |
364 | |
365 | Template::~Template() |
366 | { |
367 | delete _parts; |
368 | } |
369 | |
370 | |
371 | void Template::parse() |
372 | { |
373 | File file(_templatePath); |
374 | if (file.exists()) |
375 | { |
376 | FileInputStream fis(_templatePath.toString()); |
377 | parse(fis); |
378 | } |
379 | } |
380 | |
381 | |
382 | void 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 | |
579 | std::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 | |
601 | std::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 | |
634 | std::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 | |
647 | std::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 | |
669 | void 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 | |
679 | std::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 | |
695 | void Template::render(const Var& data, std::ostream& out) const |
696 | { |
697 | _parts->render(data, out); |
698 | } |
699 | |
700 | |
701 | } } // namespace Poco::JSON |
702 | |