Loading...
Searching...
No Matches
XML.h
1/*
2
3MIT License
4
5Copyright (c) 2017 FMI Open Development / Markus Peura, first.last@fmi.fi
6
7Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in all
15copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23SOFTWARE.
24
25 */
26/*
27Part of Rack development has been done in the BALTRAD projects part-financed
28by the European Union (European Regional Development Fund and European
29Neighbourhood Partnership Instrument, Baltic Sea Region Programme 2007-2013)
30 */
31/*
32 * TreeXML.h
33 *
34 * Created on: Jun 24, 2012
35 * Author: mpeura
36 */
37
38
39
40#ifndef DRAIN_XML
41#define DRAIN_XML
42
43#include <ostream>
44
45#include <drain/FlexibleVariable.h>
46#include <drain/MapTools.h> // NEW
47#include <drain/Sprinter.h>
48#include <drain/StringTools.h>
49
50#include "ClassXML.h"
51#include "ReferenceMap.h"
52#include "StyleXML.h"
53
54namespace drain {
55
56
57
59
69class XML : protected std::map<std::string,FlexibleVariable> { // ReferenceMap2<FlexibleVariable> {
70
71public:
72
81 typedef int intval_t;
82
83 // TODO:
84 // static const intval_t flag_OPEN = 128;
85 // static const intval_t flag_TEXT = 256;
86
87 /* "basetype" for COMMENTs and SCOPEs
88 * 0 - XML
89 * 1 - Style and C/C++
90 * 2 - parentheses
91 * 3 - braces
92 */
93protected:
94 static const intval_t XML_LIKE = 0;
95 static const intval_t CURLY = 1; // || flag_TEXT
96 static const intval_t PARENTHESES = 2; // || flag_TEXT
97 static const intval_t BRACES = 3; // || flag_EXPLICIT || flag_TEXT
98
99
100public:
101
102 static const intval_t UNDEFINED = 0;
103 static const intval_t COMMENT = 1; // || flag_TEXT
104 static const intval_t CTEXT = 2; // || flag_TEXT
105 static const intval_t SCRIPT = 3; // || flag_EXPLICIT || flag_TEXT
106 static const intval_t STYLE = 4; // || flag_EXPLICIT
107 static const intval_t STYLE_SELECT = 5;
108 static const intval_t JAVASCRIPT = 6; // future extension
109 static const intval_t SCOPE = 8; // future extension
110 static const intval_t SCOPE_CURLY = SCOPE|CURLY ; // JavaScript,
111 static const intval_t XMLBASE = 10; // for debugging basic XML doc
112
113 typedef std::map<std::string,FlexibleVariable> map_t;
114
115protected:
116
117 static
118 const StyleXML curlyStyleXML;
119
120 static const std::string SCOPE_BEGIN;
121 static const std::string SCOPE_END;
122 static const std::string COMMENT_BEGIN;
123 static const std::string COMMENT_END;
124
125public:
126
127 map_t & getMap(){
128 return *this;
129 }
130
131 const map_t & getMap() const {
132 return *this;
133 }
134
135
136 enum entity_t {
137 AMPERSAND = '&',
138 LESS_THAN = '<',
139 EQUAL_TO = '=',
140 GREATER_THAN = '>',
141 NONBREAKABLE_SPACE = ' ',
142 QUOTE = '"',
143 CURLY_LEFT = '{',
144 CURLY_RIGHT = '}',
145 // ---
146 TAB = '\t',
147 NEWLINE = '\n',
148 // ---
149 DEGREE = 176,
150 };
151
153 static
154 const std::string USER_ATTR_PREFIX;
155
156 /* Variants of entity map */
157
159 static
160 const std::map<char,std::string> & getKeyConversionMap();
161
163 static
164 const std::map<char,std::string> & getAttributeConversionMap();
165
167 static
168 const std::map<char,std::string> & getCTextConversionMap();
170
171
172 inline
173 XML(){};
174
175 // TODO: CHECK: base std::map<std::string,FlexibleVariable> (the attribute map) is explicitly
176 // default-constructed (empty) here, same as before, and the empty body means copying an XML
177 // object silently drops all its attributes. See also the "RISKY!?" note in NodeXML's copy
178 // constructor in TreeXML.h, which relies on this. Confirm intentional.
179 inline
180 XML(const XML &) : std::map<std::string,FlexibleVariable>(){
181 };
182
183 virtual inline
184 ~XML(){};
185
190 virtual
191 const std::string & getTag() const = 0;
192
194
197 // Could be also internal, and only NodeXML<> declares this interface.
198 template <class T> // "final"
199 inline
200 void setType(const T &t){ // DANGER, without cast?
201 setType(static_cast<intval_t>(t));
202 }
203
205
208 void setType(intval_t t);
209
210 inline
211 const intval_t & getType() const {
212 return type;
213 };
214
216
219 inline
220 bool typeIsSet() const {
221 return type != UNDEFINED;
222 };
223
225
228 template <class T2, class ...T3>
229 inline
230 bool typeIs(const T2 & arg, const T3... args) const {
231 if (type == static_cast<intval_t>(arg)){
232 return true;
233 }
234 else {
235 return typeIs(args...);
236 }
237 }
238
239
240
241 std::string ctext;
242
243 // Could be templated, behind Static?
244 static int nextID;
245
246 static inline
247 int getCount(){
248 return nextID;
249 }
250
251 static inline
252 int getNewIndex(){
253 return ++nextID;
254 }
255
257
260 inline
261 const std::string & setId(){
262 if (id.empty()){
263 id=drain::StringBuilder<>(getTag(), ++nextID);
264 }
265 getMap()["id"].link(id);
266 return id;
267 }
268
270 inline
271 const std::string & setId(const std::string & s){
272 getMap()["id"].link(id=s);
273 return id;
274 }
275
277 template <char C='\0', typename ...TT>
278 inline
279 const std::string & setId(const TT & ...args) {
280 getMap()["id"].link(id=drain::StringBuilder<C>(args...));
281 return id;
282 }
283
285 inline
286 const std::string & getId() const {
287 return id;
288 }
289
290 inline
291 bool isUndefined() const {
292 return type == UNDEFINED;
293 }
294
295 inline
296 bool isComment() const {
297 return type == COMMENT;
298 }
299
300 inline
301 bool isCText() const {
302 return type == CTEXT;
303 }
304
305 inline
306 bool isStyle() const {
307 return type == STYLE;
308 }
309
310 /* HTML Only
311 inline
312 bool isLink() const {
313 return type == LINK;
314 }
315 */
316
317 inline
318 bool isScript() const {
319 return type == SCRIPT;
320 }
321
322 inline
323 bool isScopePlain() const {
324 return type == SCOPE;
325 }
326
327 inline
328 bool isScopeJS() const {
329 return type == SCOPE_CURLY;
330 }
331
332 inline
333 bool isScope() const {
334 return isScopePlain() || isScopeJS();
335 }
336
337
339 virtual
340 bool isSingular() const;
341
343 virtual
344 bool isExplicit() const;
345
347
355 void reset();
365 virtual
366 void swapXML(XML & node);
367
369
371 template <class ...T>
372 inline
373 void setComment(const T & ...args) {
374 this->reset();
375 //this->clear(); // what if also uncommenting needed?
376 type = COMMENT;
377 setText(args...);
378 }
379
381 template <class ...T>
382 inline
383 void setCommentSafe(const T & ...args) {
384 this->reset();
385 //this->clear(); // what if also uncommenting needed?
386 type = COMMENT;
387 setTextSafe(args...);
388 }
389
391
396 virtual // redef shows variadic args, below?
397 void setText(const std::string & s);
398
399 template <class ...T>
400 void setText(const T & ...args) {
401 setText(StringBuilder<>(args...).str()); // str() to avoid infinite loop
402 }
403
405
414 template <class ...T>
415 void setTextSafe(const T & ...args) {
416 std::string dst;
417 StringTools::replace(StringBuilder<>(args...).str(), getCTextConversionMap(), dst); // str() to avoid infinite loop
418 setText(dst);
419 //setText(StringTools::replace(m, StringBuilder<>(args...).str(), ctext)); // str() to avoid infinite loop
420 }
421
423
432 template <class ...T>
433 inline
434 void setProgramComment(const T & ...args) {
435 setText('/', '*', ' ', args..., ' ', '*', '/');
436 }
437
438
439 virtual inline
440 const std::string & getText() const {
441 return ctext;
442 }
443
444 inline
445 const std::string & getUrl(){
446 return url;
447 }
448
452 virtual inline
453 void setUrl(const std::string & s){
454 url = s;
455 if (isScript()){
456 getMap()["url"].link(url);
457 // link("url", url);
458 }
459 }
460
461 template <class ...T>
462 inline
463 void setName(const T & ...args){
464 setAttribute("data-name", drain::StringBuilder<>(args...).str());
465 }
466
467 inline
468 const FlexibleVariable & getName() const {
469 return MapTools::get(getMap(), std::string("data-name"));
470 // return (*this)["data-name"];
471 }
472
473
474 // ---------------- Attributes ---------------
475
476 virtual inline // shadows - consider virtual
477 bool empty() const {
478 return map_t::empty();
479 }
480
481 inline
482 bool hasAttribute(const std::string & key){
483 return MapTools::hasKey(getMap(), key);
484 };
485
486 inline
487 bool hasUserAttribute(const std::string & key){
488 return MapTools::hasKey(getMap(), USER_ATTR_PREFIX+key);
489 };
490
491 inline
492 const map_t & getAttributes() const {
493 return *this;
494 };
495
496 // Maybe controversial. Helps importing sets of variables.
497 inline
498 map_t & getAttributes(){
499 return *this;
500 };
501
502 // Rename getAttribute?
503 inline
504 const drain::FlexibleVariable & getAttribute(const std::string & key) const {
505 return MapTools::get(getMap(), key);
506 //return (*this)[key];
507 }
508
509 // Rename getAttribute?
510 inline
511 drain::FlexibleVariable & getAttribute(const std::string & key){
512 return MapTools::get(getMap(), key);
513 // return (*this)[key];
514 }
515
516
517 // Rename getAttribute?
518 template <class V>
519 inline
520 V getAttribute(const std::string & key, const V & defaultValue) const {
521 return MapTools::get(getMap(), key, defaultValue);
522 //return map_t::get(key, defaultValue);
523 }
524
525 // Rename getAttribute?
526 inline
527 std::string getAttribute(const std::string & key, const char * defaultValue) const {
528 return MapTools::get(getMap(), key, defaultValue);
529 //return map_t::get(key, defaultValue);
530 }
531
532 inline
533 const drain::FlexibleVariable & getUserAttribute(const std::string & key) const {
534 return getAttribute(USER_ATTR_PREFIX+key);
535 }
536
537 inline
538 drain::FlexibleVariable & getUserAttribute(const std::string & key){
539 return getAttribute(USER_ATTR_PREFIX+key);
540 }
541
542 template <class V>
543 inline
544 V getUserAttribute(const std::string & key, const V & defaultValue) const {
545 return getAttribute(USER_ATTR_PREFIX+key, defaultValue);
546 }
547
548 inline
549 std::string getUserAttribute(const std::string & key, const char *defaultValue) const {
550 return getAttribute(USER_ATTR_PREFIX+key, defaultValue);
551 }
552
554 // But otherways confusing?
555 virtual inline
556 void setAttribute(const std::string & key, const std::string &value){
557 (*this)[key] = value;
558 }
559
561 // But otherways confusing?
562 virtual inline
563 void setAttribute(const std::string & key, const char *value){
564 (*this)[key] = value; // -> handleString()
565 }
566
567 /*
568 template <class V>
569 inline
570 void setAttribute(const std::string & key, const V & value){
571 (*this)[key] = value; // -> handleString()
572 }
573 */
574
576 template <class ... TT>
577 inline
578 void setAttribute(const std::string & key, const TT &... args){
579 (*this)[key] = StringBuilder<>(args...).str();
580 }
581
582 template <class ... TT>
583 inline
584 void setUserAttribute(const std::string & key, const TT &... args){
585 setAttribute(USER_ATTR_PREFIX+key, args...);
586 }
587
588
589 inline
590 void removeAttribute(const std::string & s){
591 iterator it = this->find(s);
592 if (it != this->end()){
593 this->erase(it);
594 }
595 }
596
597
598
599protected:
600
601 inline
602 bool typeIs() const {
603 return false;
604 };
605
606 intval_t type = XML::UNDEFINED;
607
608 // String, still easily allowing numbers through set("id", ...)
609 std::string id;
610 // Consider either/or
611 std::string url;
612
613 virtual inline
614 void handleType(){}; // = 0;
615
616
617public:
618
619 // ------------------ Style ---------------
620
621protected:
622
623 StyleXML style;
624
625public:
626
627 inline
628 const StyleXML & getStyle() const {
629 return style;
630 }
631
632 Variable & getStyle(const std::string & key){
633 return style[key];
634 }
635
636 /*
637 const Variable & getStyle(const std::string & key) const {
638 return style[key];
639 }
640 */
641
642 template <class T>
643 T getStyle(const std::string & key, const T & defaultValue) const {
644 return MapTools::get(style, key, defaultValue);
645 // return style[value];
646 }
647
648
649 inline
650 void setStyle(const StyleXML & s){
651 style.clear();
652 MapTools::setValues(style, s);
653 }
654
655 void setStyle(const std::string & value){
656 drain::Logger mout(__FILE__, __FUNCTION__);
657 if (type == UNDEFINED){
658 mout.reject<LOG_WARNING>("setting style for UNDEFINED elem: ", value);
659 mout.unimplemented<LOG_WARNING>("future option: set type to STYLE_SELECT");
660 }
661 else if (type == STYLE){
662 mout.reject<LOG_WARNING>("not setting style for STYLE elem: ", value); // , *this);
663 }
664 else {
665 MapTools::setValues(style, value, ';', ':', "; \t\n"); // sep, equal, trim (also ';' ?)
666 }
667 }
668
669 inline
670 void setStyle(const char *value){
671 setStyle(std::string(value));
672 }
673
674 inline
675 void setStyle(const std::string & key, const std::string & value){
676 drain::Logger mout(__FILE__, __FUNCTION__);
677 if (type == UNDEFINED){
678 mout.reject<LOG_WARNING>("setting style for UNDEFINED elem: ", key, '=', value);
679 mout.unimplemented<LOG_WARNING>("future option: set type to STYLE_SELECT");
680 }
681 else if (type == STYLE){
682 mout.reject<LOG_WARNING>("not setting style for STYLE elem: ", value); // , *this);
683 }
684 else {
685 this->style[key] = value;
686 }
687 }
688
689
691
697 template <class V>
698 inline
699 void setStyle(const std::string & key, const std::initializer_list<V> &l){
700 // const std::initializer_list<Variable::init_pair_t > &l
701 if (type == STYLE){ // typeIs(STYLE) fails
702 drain::Logger mout(__FILE__, __FUNCTION__);
703 mout.warn("Setting style of STYLE? initializer_list<", drain::TypeName<V>::str(), "> = ", sprinter(l)); // , StyleXML::styleLineLayout ?
704 }
705 this->style[key] = l;
706 }
707
708
710
720
728 template <class ... TT>
729 inline
730 void setStyle(const std::string & key, const TT &... args){
731
732 if (type == STYLE){
733 drain::Logger(__FILE__, __FUNCTION__).reject<LOG_WARNING>("Setting style of STYLE: ", key, "=", args...);
734 }
735 else {
736 // In a variable map
737 this->style[key] = StringBuilder<>(args...).str();
738 }
739 }
740
741 inline
742 void setStyle(const std::initializer_list<std::pair<const char *,const drain::Variable> > &args){
743 //drain::SmartMapTools::setValues(style, args);
744 drain::MapTools::setValues(style, args);
745 }
746
747 template <class V>
748 void setStyle(const std::map<std::string, V> & args){
749 drain::MapTools::setValues(style, args);
750 }
751
752
753
754protected:
755
756 // ------------------ Style Class ---------------
757
758 ClassListXML classList;
759
760public:
761
762 const ClassListXML & getClasses() const {
763 return classList;
764 }
765
766 template <typename ... TT>
767 inline
768 void addClass(const TT &... args) {
769 classList.add(args...);
770 }
771
772 inline
773 void addClass(const ClassListXML & clist) {
774 for (auto & cls: clist){
775 addClass(cls);
776 }
777 }
778
782 template <class V>
783 inline
784 bool hasClass(const V & cls) const {
785 return classList.has(cls);
786 }
787
788
789 template <class V>
790 inline
791 void removeClass(const V & cls) {
792 classList.remove(cls);
793 }
794
795 inline
796 void clearClasses(){
797 classList.clear();
798 }
799
800
801 virtual
802 void specificAttributesToStream(std::ostream & ostr) const;
803
804
805 enum tag_display_mode {
806 FLEXIBLE_TAG = 0, // <TAG>...</TAG> or <TAG/>
807 OPENING_TAG= 1, // <TAG>
808 CLOSING_TAG = 2, // </TAG>
809 EMPTY_TAG = OPENING_TAG | CLOSING_TAG, // element has no descendants: <hr/>
810 NON_EMPTY_TAG, // opening and closing tags must appear, even when empty: <script></script>
811 NO_TAG, // New, for CTEXT
812 };
813
814
815 virtual
816 std::ostream & nodeToStream(std::ostream & ostr, tag_display_mode mode=EMPTY_TAG) const = 0;
817
818
819 /*
820 template <class TR>
821 static
822 std::ostream & toStream(std::ostream & ostr, const TR & tree, const std::string & defaultTag="ELEM", int indent=0);
823 */
824
825 // , const std::string & defaultTag="ELEM" UnorderedMultiTree<N>
826 template <class N>
827 static
828 std::ostream & toStream(std::ostream & ostr, const UnorderedMultiTree<N> & tree, int indent=0);
829
830 template <class N>
831 static
832 std::ostream & styleToStream(std::ostream & ostr, const UnorderedMultiTree<N> & tree, int indent=0);
833
834 template <class N>
835 static
836 std::ostream & textToStream(std::ostream & ostr, const UnorderedMultiTree<N> & tree, int indent=0);
837
838
840
842 static
843 const std::map<char,std::string> & getEntityMap();
844
846
850 // core utility (not to be external)
851 template <class V>
852 static inline
853 void xmlAttribToStream(std::ostream &ostr, const std::string & key, const V &value){
854 //StringTools::replace(XML::encodingMap, data.ctext, ostr);
855 //ostr << ' ' << key << '=' << '"' << value << '"'; // << ' ';
856
857 static const std::map<char,char> keyMap = {
858 {' ','_'},
859 {'"','_'},
860 {'=','_'},
861 };
862 ostr << ' ';
863 StringTools::replace(key, keyMap, ostr); // XML::encodingMap
864 //StringTools::replace(getEntityMap(), key, ostr); // XML::encodingMap
865
866 static const std::map<char,std::string> valueMap = {
867 {entity_t::QUOTE, "'"},
868 {entity_t::LESS_THAN,"(("},
869 {entity_t::GREATER_THAN,"))"},
870 };
871 ostr << '=' << '"';
872 StringTools::replace(value, valueMap, ostr); // XML::encodingMap
873 //StringTools::replace(getEntityMap(), value, ostr); // XML::encodingMap
874 ostr << '"';
875 //<< key << '=' << '"' << value << '"'; // << ' ';
876 }
877
878
880
885 template <typename N>
886 static inline
887 N & xmlAssignNode(N & dst, const N & src){
888
889 if (&src != &dst){
890 //dst.clear(); // clear attributes,
891 //if (!dst.typeIs(src.getNativeType())){
892 if (dst.getType() != src.getType()){
893 dst.reset(); // clear attributes, style, cstring and type.
894 // Warning: does not create links.
895 dst.setType(src.getType());
896 }
897 // dst.getAttributes().importMap(src.getAttributes());
898 MapTools::setValues(dst.getAttributes(), src.getAttributes());
899 dst.setStyle(src.getStyle());
900 // dst.setText(src.ctext); // wrong! set type to CTEXT
901 dst.ctext = src.ctext;
902 }
903
904 return dst;
905 }
906
907
908
909 /*
910 UNUSED!
911 template <typename T>
912 static
913 T & xmlGuessType(const typename T::node_data_t & parentNode, T & child){
914 typedef typename T::node_data_t::xml_default_elem_map_t map_t;
915 const typename map_t::const_iterator it = T::node_data_t::xml_default_elems.find(parentNode.getNativeType());
916 if (it != T::node_data_t::xml_default_elems.end()){
917 child->setType(it->second);
918 drain::Logger(__FILE__, __FUNCTION__).experimental<LOG_WARNING>("Default type set: ", child->getTag());
919 }
920 return child;
921 }
922 */
923
924
925
926};
927
928
929// template <> const drain::Enum<XML::entity_t>::dict_t drain::Enum<XML::entity_t>::dict;
930DRAIN_ENUM_DICT(XML::entity_t);
931DRAIN_ENUM_OSTREAM(XML::entity_t)
932
933template <class N>
934std::ostream & XML::textToStream(std::ostream & ostr, const UnorderedMultiTree<N> & tree, int indent){
935
936 drain::Logger mout(__FILE__,__FUNCTION__);
937
938 std::string fill(2*indent, ' ');
939
940 typedef UnorderedMultiTree<N> TR;
941 const typename TR::node_data_t & data = tree.data;
942
943 char sep = 0;
944
945 // if (!data.ctext.empty()){
946 ostr << fill << data.ctext;
947 // }
948
949 if (data.isScopeJS()){
950 ostr << "{";
951 if (tree.hasChildren()){
952 ostr << '\n';
953 }
954 sep = '\n';
955 }
956
957 if (indent > 0){
958 indent += 2;
959 }
960
961 for (const auto & entry: tree.getChildren()){
962 textToStream(ostr, entry.second, indent);
963 if (sep){
964 ostr << sep;
965 }
966
967 }
968
969 if (data.isScopeJS()){
970 ostr << fill << "}"; // Note: semicolon not supported at end of argument, consider: .forEach( elem => {});
971 }
972
973 return ostr;
974}
975
976
977template <class N>
978std::ostream & XML::styleToStream(std::ostream & ostr, const UnorderedMultiTree<N> & tree, int indent){
979
980 drain::Logger mout(__FILE__,__FUNCTION__);
981
982 std::string fill(2*indent, ' ');
983
984 typedef UnorderedMultiTree<N> TR;
985 const typename TR::node_data_t & data = tree.data;
986
987 if (!data.ctext.empty()){
988 // TODO: indent
989 ostr << fill << data.ctext;
990 StyleXML::commentToStream(ostr, " TEXT ");
991 ostr << '\n';
992 }
993
994 if (!data.getAttributes().empty()){
995 mout.warn("STYLE elem ", data.getId()," contains attributes, probably meant as style: ", sprinter(data.getAttributes()));
996 ostr << "\n\t /" << "* <!-- DISCARDED attribs ";
997 Sprinter::toStream(ostr, data.getAttributes()); //, StyleXML::styleRecordLayout
998 ostr << " /--> *" << "/" << '\n';
999 }
1000
1001 if (!data.style.empty()){
1002 ostr << fill;
1003 StyleXML::commentToStream(ostr, "STYLE OBJ");
1004 ostr << '\n';
1005 for (const auto & attr: data.style){
1006 ostr << fill << " ";
1007 Sprinter::pairToStream(ostr, attr, StyleXML::styleRecordLayout); // {" :;"}
1008 //attr.first << ':' attr.first << ':';
1009 ostr << '\n';
1010 }
1011 // ostr << fill << "}\n";
1012 // Sprinter::sequenceToStream(ostr, entry.second->getAttributes(), StyleXML::styleRecordLayoutActual);
1013 // ostr << '\n';
1014 }
1015 ostr << '\n';
1016
1017 ostr << fill;
1018 StyleXML::commentToStream(ostr, "style ELEMS");
1019 ostr << '\n';
1020
1021 for (const auto & entry: tree.getChildren()){
1022
1023 if (entry.second->isComment()){
1024 // StringTools::replace();
1025 ostr << fill << "/* "<< entry.second->ctext << " */" << '\n';
1026 // XML::toStream(ostr, entry.second, defaultTag, indent); // indent not used?
1027 continue;
1028 }
1029
1030 if (!entry.second->ctext.empty()){
1031 //ostr << fill << "<!-- elem("<< entry.first << ") ctext /-->" << '\n';
1032 ostr << fill << " " << entry.first << " {" << entry.second->ctext << "} /* CTEXT */ \n";
1033 }
1034
1035 if (!entry.second->getAttributes().empty()){
1036 ostr << fill << " " << entry.first << " {\n";
1037 for (const auto & attr: entry.second->getAttributes()){
1038 ostr << fill << " ";
1039 if (attr.first == "id"){
1040 StyleXML::commentToStream(ostr, "id=", attr.second);
1041 // mout.suspicious("Style elem has id (", attr.second, ")");
1042 }
1043 else {
1044 ostr << attr.first << ':' << attr.second << ';';
1045 }
1046 ostr << '\n';
1047 }
1048 ostr << fill << " }\n";
1049 ostr << '\n';
1050 }
1051
1052 }
1053 ostr << "\n"; // end CTEXT
1054 // ostr << " ]]>\n"; // end CTEXT
1055 // end STYLE defs
1056 ostr << fill;
1057
1058 return ostr;
1059}
1060
1061// drain::UnorderedMultiTree<NodeSVG,false, NodeXML<>::path_t> TreeSVG;
1062// , const std::string & defaultTag
1063// template <class TR>
1064template <class N>
1065std::ostream & XML::toStream(std::ostream & ostr, const UnorderedMultiTree<N> & tree, int indent){
1066
1067 drain::Logger mout(__FILE__,__FUNCTION__);
1068
1069 typedef UnorderedMultiTree<N> TR;
1070
1071 const typename TR::container_t & children = tree.getChildren();
1072
1073 // const XML & data = tree.data; // template type forcing, keep here for programming aid.
1074 const typename TR::node_data_t & data = tree.data; // template used
1075
1076 tag_display_mode mode = EMPTY_TAG;
1077
1079 if (data.isCText()){ // this can be true only at root, and rarely so...? (because recursive call not reached, if ctext)
1080 textToStream(ostr, tree, indent);
1081 return ostr;
1082 }
1083 else if (data.isScopeJS()){ // this can be true only at root, and rarely so...? (because recursive call not reached, if ctext)
1084 // ostr << '{';
1085 textToStream(ostr, tree, indent);
1086 // ostr << '}' << ';' << '\n';
1087 return ostr;
1088 }
1089 else if (data.isCText()){ // this can be true only at root, and rarely so...? (because recursive call not reached, if ctext)
1090 // data.nodeToStream(ostr, mode);
1091 // ostr << "<!--TX-->";
1092 // NEW
1093 mode = NO_TAG;
1094 // return ostr;
1095 }
1096 else {
1097 if (!data.ctext.empty()){
1098 // mout.warn("Non-CTEXT-elem with ctext: <", data.getTag(), " id='", data.getId(), "' ...>, text='", data.ctext, "'");
1099 if (data.isSingular()){
1100 // mout.warn("Skipping CTEXT of a singular element <", tree->getTag(), " id='", data.getId(), "' ...> , CTEXT: '", data.ctext, "'");
1101 mode = EMPTY_TAG;
1102 }
1103 else {
1104 mode = OPENING_TAG;
1105 }
1106 }
1107
1108 if (!children.empty()){
1109 mode = OPENING_TAG;
1110 if (data.isSingular()){
1111 mout.warn("Singular (hence normally empty) element <", tree->getTag(), " id='", data.getId(), "' ...> has ", children.size(), " children?");
1112 }
1113 }
1114
1115 // mout.attention("Hey! ", TypeName<TR>::str(), " with ", TypeName<typename TR::node_data_t>::str(), "Explicit ", data.isExplicit(), " or implicit ", data.isSingular());
1116 if (data.isExplicit()){ // explicit
1117 mode = OPENING_TAG;
1118 }
1119 else if (data.isSingular()){ // <br/> <hr/>
1120 mode = EMPTY_TAG;
1121 }
1122
1123 // Hence, is flexible, "bimodal", supports empty and open-close mode.
1124
1125 }
1126
1127
1128
1129 // Indent
1130 std::string fill(2*indent, ' ');
1131 if (!data.isScopeJS()){
1132 ostr << fill;
1133 }
1134
1135
1136 // Print opening tag and attributes.
1137 // NEW: js scope print also its CTEXT
1138 data.nodeToStream(ostr, mode);
1139
1140 // Note: if tree has attributes, empty() == false.
1141 //if (data.isScript() && !(tree.empty() && data.ctext.empty())){ // (mode==OPENING_TAG)
1142 const bool SCRIPT_NONEMPTY = data.isScript() && (tree.hasChildren() || !data.ctext.empty());
1143 //if (data.isScript() && (tree.hasChildren() || !data.ctext.empty())){
1144 if (SCRIPT_NONEMPTY){
1145 //std::string fill(2*indent, ' ');
1146 ostr << "//<![CDATA[\n";
1147 }
1148
1149 // TODO: consider unintended, "packed" class for TSPAN etc.
1150
1151 if (mode == EMPTY_TAG){
1152 // ostr << "<!--ET-->";
1153 // if (!SCRIPT_NONEMPTY){
1154 ostr << '\n';
1155 // }
1156 return ostr;
1157 }
1158 else if (data.isStyle()){
1159
1160 styleToStream(ostr, tree, indent);
1161
1162 // https://www.w3.org/TR/xml/#sec-cdata-sect
1163 // ostr << "<![CDATA[ \n";
1164 // ostr << "<!-- STYLE -->"; WRONG!
1165 /*
1166 if (!data.ctext.empty()){
1167 // TODO: indent
1168 ostr << fill << data.ctext;
1169 StyleXML::commentToStream(ostr, " TEXT ");
1170 ostr << '\n';
1171 }
1172
1173 if (!data.getAttributes().empty()){
1174 mout.warn("STYLE elem ", data.getId()," contains attributes, probably meant as style: ", sprinter(data.getAttributes()));
1175 ostr << "\n\t /" << "* <!-- DISCARDED attribs ";
1176 Sprinter::toStream(ostr, data.getAttributes()); //, StyleXML::styleRecordLayout
1177 ostr << " /--> *" << "/" << '\n';
1178 }
1179
1180 if (!data.style.empty()){
1181 ostr << fill;
1182 StyleXML::commentToStream(ostr, "STYLE OBJ");
1183 ostr << '\n';
1184 for (const auto & attr: data.style){
1185 ostr << fill << " ";
1186 Sprinter::pairToStream(ostr, attr, StyleXML::styleRecordLayout); // {" :;"}
1187 //attr.first << ':' attr.first << ':';
1188 ostr << '\n';
1189 }
1190 // ostr << fill << "}\n";
1191 // Sprinter::sequenceToStream(ostr, entry.second->getAttributes(), StyleXML::styleRecordLayoutActual);
1192 // ostr << '\n';
1193 }
1194 ostr << '\n';
1195
1196 ostr << fill;
1197 StyleXML::commentToStream(ostr, "style ELEMS");
1198 ostr << '\n';
1199
1200 for (const auto & entry: tree.getChildren()){
1201
1202 if (entry.second->isComment()){
1203 // StringTools::replace();
1204 ostr << fill << "/ * "<< entry.second->ctext << " * /" << '\n';
1205 // XML::toStream(ostr, entry.second, defaultTag, indent); // indent not used?
1206 continue;
1207 }
1208
1209 if (!entry.second->ctext.empty()){
1210 //ostr << fill << "<!-- elem("<< entry.first << ") ctext /-->" << '\n';
1211 ostr << fill << " " << entry.first << " {" << entry.second->ctext << "} / * CTEXT * / \n";
1212 }
1213
1214 if (!entry.second->getAttributes().empty()){
1215 ostr << fill << " " << entry.first << " {\n";
1216 for (const auto & attr: entry.second->getAttributes()){
1217 ostr << fill << " ";
1218 if (attr.first == "id"){
1219 StyleXML::commentToStream(ostr, "id=", attr.second);
1220 // mout.suspicious("Style elem has id (", attr.second, ")");
1221 }
1222 else {
1223 ostr << attr.first << ':' << attr.second << ';';
1224 }
1225 ostr << '\n';
1226 }
1227 ostr << fill << " }\n";
1228 ostr << '\n';
1229 }
1230
1231 }
1232 ostr << "\n"; // end CTEXT
1233 // ostr << " ]]>\n"; // end CTEXT
1234 // end STYLE defs
1235 ostr << fill;
1236 */
1237 }
1238 else {
1239
1240 // Elements "own" CTEXT will be always output first -> check for problems, if other elements added first.
1241 // ostr << data.ctext;
1242 /*
1243 if (data.isScript()){
1244 ostr << data.ctext; // let < and > pass through
1245 }
1246 else {
1247 ostr << data.ctext; // let < and > pass through
1248 // StringTools::replace(XML::encodingMap, data.ctext, ostr); // sometimes an issue? // any time issue?
1249 }
1250 */
1251
1252 /*
1253 if (!data.isCText()){
1254 ostr << data.ctext;
1255 }
1256 */
1257
1258 ostr << data.ctext;
1259 /*
1260 if (!data.isScopeJS()){
1261 if (!data.ctext.empty()){
1262 //ostr << data.ctext << '\n'; // newline nice in scripts, but else where?
1263 }
1264 }
1265 */
1266
1267 // Detect if all the children are of type CTEXT, to be rendered in a single line.
1268 // Note: potential re-parsing will probably detect them as a single CTEXT element.
1269 bool ALL_CTEXT = true; // (!data.ctext.empty()) || !children.empty();
1270
1271 for (const auto & entry: children){
1272 // New: if CTEXT has children, ensure recursion by skipping ALL_TEXT handler
1273 if (entry.second.hasChildren() || !entry.second->isCText()){
1274 ALL_CTEXT = false;
1275 break;
1276 }
1277 }
1278
1279 if (ALL_CTEXT){
1280 // ostr << "<!--ALL_CTEXT-->";
1281 char sep=0;
1282 for (const auto & entry: children){
1283 if (sep){
1284 ostr << sep;
1285 }
1286 else {
1287 sep = ' '; // consider global setting?
1288 }
1289
1290 if (data.isScript()){
1291 // Unfiltered
1292 ostr << entry.second->getText();
1293 }
1294 else {
1295 StringTools::replace(entry.second->getText(), getEntityMap(), ostr); // any time issue?
1296 }
1297
1298 }
1299 }
1300 else {
1301 //ostr << "<!-- RECURSION -->";
1302 if (!data.isScopeJS()){
1303 ostr << '\n';
1304 }
1306 for (const auto & entry: children){
1307 //toStream(ostr, entry.second, entry.first, indent+1); // Notice, no ++indent
1308 toStream(ostr, entry.second, indent+1); // Notice, no ++indent
1309 // "implicit" newline
1310 }
1311 ostr << fill; // for CLOSING tag
1312 }
1313
1314 if (data.isCText())
1315 return ostr;
1316 }
1317
1318 // ostr << "<!-- END "<< data.getId() << ' ' << data.getTag() << '(' << data.getType() << ')' << "-->";
1319 // Note: if tree has attributes, empty() == false.
1320 // Write terminting escape sequence only if there was some content
1321 if (SCRIPT_NONEMPTY){
1322 // if (data.isScript() && (tree.hasChildren() || !data.ctext.empty())){
1323 // if (data.isScript() && !(tree.empty() && data.ctext.empty())){
1324 ostr << '\n';
1325 std::string fill(2*indent, ' ');
1326 ostr << "//]]>";
1327 }
1328
1329
1330 data.nodeToStream(ostr, CLOSING_TAG);
1331 // if (!data.isScopeJS()){
1332 ostr << '\n'; // Always after closing tag!
1333 //}
1334
1335
1336 return ostr;
1337}
1338
1339#define DRAIN_XML_NODE_SWAP(tree_t) template <> inline void tree_t::swapData(tree_t::node_data_t & node){ this->data.swapXML(node); }
1340
1341
1342} // drain::
1343
1344#endif /* DRAIN_XML */
1345
LogSourc e is the means for a function or any program segment to "connect" to a Log.
Definition Log.h:326
Logger & warn(const TT &... args)
Possible error, but execution can continue.
Definition Log.h:440
Logger & reject(const TT &... args)
Some input has been rejected, for example by a syntax.
Definition Log.h:620
static void setValues(M &dst, const std::map< std::string, S > &srcMap)
Definition MapTools.h:211
static const M::mapped_type & get(const M &m, const typename M::key_type &key)
If the key is found, the value is returned as a reference.
Definition MapTools.h:90
static std::ostream & toStream(std::ostream &ostr, const std::initializer_list< T > &x, const SprinterLayout &layout=defaultLayout)
New (experimental)
Definition Sprinter.h:423
Definition StringBuilder.h:58
const std::string & str() const
For explicit string cast, esp. for (skipping) template deduction.
Definition StringBuilder.h:102
static void replace(const std::string &src, char search, char repl, std::ostream &ostr)
Replaces instances of 'search' to 'repl' in src.
Definition StringTools.cpp:93
Definition StyleXML.h:57
static void commentToStream(std::ostream &ostr, const TT... args)
Practical utility, helps in adding C++ code commenting...
Definition StyleXML.h:99
VariableT is a final class applied through typedefs Variable, Reference and FlexibleVariable.
Definition VariableT.h:87
Base class for XML "nodes", to be data elements T for drain::Tree<T>
Definition XML.h:69
static const std::map< char, std::string > & getEntityMap()
Handy map for converting characters to XML entities. Example: '&' -> "&".
Definition XML.cpp:283
bool typeIs(const T2 &arg, const T3... args) const
Return true, if type is any of the arguments.
Definition XML.h:230
virtual bool isExplicit() const
Tell if this element should always have an explicit closing tag even when empty, like <STYLE></STYLE>
Definition XML.cpp:199
bool typeIsSet() const
Return true, if type is any of the arguments.
Definition XML.h:220
const std::string & setId(const TT &...args)
Concatenates arguments to an id.
Definition XML.h:279
const std::string & setId()
Makes ID a visible attribute.
Definition XML.h:261
static const std::map< char, std::string > & getKeyConversionMap()
Characters that must be avoided in XML attribute keys: space, tab, =.
Definition XML.cpp:114
virtual bool isSingular() const
Tell if this element should always have an explicit closing tag even when empty, like <STYLE></STYLE>
Definition XML.cpp:193
virtual void swapXML(XML &node)
Definition XML.cpp:239
static const std::string USER_ATTR_PREFIX
In XML variants, like SVG, it is possible to use any user defined attributes using prefix "data-" .
Definition XML.h:154
virtual const std::string & getTag() const =0
void setStyle(const std::string &key, const std::initializer_list< V > &l)
Set style of an element.
Definition XML.h:699
const std::string & getId() const
Returns ID of this element. Hopefully a unique ID...
Definition XML.h:286
virtual void setAttribute(const std::string &key, const char *value)
Default implementation. Needed for handling units in strings, like "50%" or "640px".
Definition XML.h:563
void setProgramComment(const T &...args)
Sets text surrounded by C language comment marks.
Definition XML.h:434
static const std::map< char, std::string > & getAttributeConversionMap()
Characters that must be avoided in XML attribute values: ".
Definition XML.cpp:129
int intval_t
Definition XML.h:81
void setCommentSafe(const T &...args)
Sets XML style comment <--...--> of concatenated arguments.
Definition XML.h:383
const std::string & setId(const std::string &s)
Makes ID a visible attribute, with a given value.
Definition XML.h:271
void setAttribute(const std::string &key, const TT &... args)
"Final" implementation.
Definition XML.h:578
static const std::map< char, std::string > & getCTextConversionMap()
Characters to avoid in XML free text: <, >, {, },.
Definition XML.cpp:141
static void xmlAttribToStream(std::ostream &ostr, const std::string &key, const V &value)
Handy map for converting characters to XML entities. Example: '&' -> "&".
Definition XML.h:853
void setTextSafe(const T &...args)
Sets slightly converted text, ensuring some XML entities.
Definition XML.h:415
virtual void setAttribute(const std::string &key, const std::string &value)
Default implementation. Needed for handling units in strings, like "50%" or "640px".
Definition XML.h:556
static std::ostream & toStream(std::ostream &ostr, const UnorderedMultiTree< N > &tree, int indent=0)
Definition XML.h:1065
void setType(const T &t)
Definition XML.h:200
static N & xmlAssignNode(N &dst, const N &src)
Assign tree node (data) to another.
Definition XML.h:887
void reset()
Keep the element type but clear style, class and string data.
Definition XML.cpp:186
void setStyle(const std::string &key, const TT &... args)
For element/class/id, assign ...
Definition XML.h:730
virtual void setText(const std::string &s)
Assign the text content of this node. If the node type is undefined, set it to CTEXT.
Definition XML.cpp:204
void setComment(const T &...args)
Make this node a comment. Contained tree will not be deleted. In current version, attributes WILL be ...
Definition XML.h:373
XML()
User may optionally filter attributes and CTEST with this using StringTools::replace(XML::encodingMap...
Definition XML.h:173
virtual void setUrl(const std::string &s)
Definition XML.h:453
bool hasClass(const V &cls) const
Definition XML.h:784
Definition Data.h:1473
VariableT< VariableInitializer< ReferenceT< VariableBase > > > FlexibleVariable
Value container supporting dynamic type with own memory and optional linking (referencing) of externa...
Definition FlexibleVariable.h:66
DRAIN_VARIABLE Variable
Value container supporting dynamic type.
Definition Variable.h:63
Default implementation.
Definition TypeName.h:54