1 /* This file is part of EdiTeX, an editor of mathematical
2 * expressions based on TeX syntax.
4 * Copyright (C) 2002-2003 Luca Padovani <lpadovan@cs.unibo.it>,
5 * 2003 Paolo Marinelli <pmarinel@cs.unibo.it>.
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 * For more information, please visit the project's home page
22 * http://helm.cs.unibo.it/editex/
23 * or send an email to <lpadovan@cs.unibo.it>
29 #include "TPushParser.hh"
30 #include "AMathMLFactory.hh"
32 TPushParser::TPushParser(ALogger& l, const TDictionary& d) : APushParser(l), dictionary(d)
37 TPushParser::TPushParser(ALogger& l, AMathMLFactory& f, const TDictionary& d) : APushParser(l, f), dictionary(d)
42 TPushParser::~TPushParser()
49 cursor = doc.create("cursor");
50 cursor["visible"] = "1";
59 if (cursor.parent()) cursor.remove();
62 doc.root().append(cursor);
63 if (factory && !frozen()) factory->documentModified(doc);
69 const TDictionary::Entry entry = dictionary.find("prime");
70 if (entry.cls == TDictionary::OPERATOR)
72 TNode op = doc.createO(entry.value, nextId++);
78 TNode op = doc.createO("?", nextId++);
84 TPushParser::do_begin()
86 TNode parent = cursor.parent();
87 if (parent.isC() && dictionary.find(parent.nameC()).table)
89 TNode row = doc.create("row");
90 TNode cell = doc.create("cell");
91 TNode g = doc.createG();
99 TNode g = doc.createG(nextId++);
107 TPushParser::correctBrace()
109 // this method MUST be invoked when the cursor is child of a
110 // phantom group, which in turn is the last rightOpen MACRO's child.
111 // The only way to exit from a rightOpen MACRO is opening a group before
112 // inserting the MACRO and, once the MACRO is completely inserted, closing
114 // This method return true if the condition above is true. False, otherwise.
115 assert(cursor.parent() && cursor.parent().isG() && !cursor.parent().hasId());
116 TNode parent = cursor.parent();
117 assert(parent.parent() && parent.parent().isC());
118 assert(!frames.empty());
119 Frame& frame = frames.top();
120 assert(frame.entry.rightOpen);
121 assert(parent.parent().last() == parent);
123 TNode c = parent.parent();
126 TNode node = c.parent();
129 if (node.isG() && node.hasId())
131 // in this case, the rightOpen MACRO is a child of a group with id.
132 // So, the '}' is correct
138 // the MACRO is a phantom group's child. We have to control why we
139 // have this phantom group
140 TNode nodeParent = node.parent();
141 if (nodeParent && nodeParent.isC())
143 // we have to control the nature of this MACRO
144 const TDictionary::Entry& entry = dictionary.find(nodeParent.nameC());
145 if (entry.rightOpen && node == nodeParent.last())
147 // in this case we have to re-iterate the process
148 node = nodeParent.parent();
156 // at the moment we assume that a MACRO cannot be child of an element other than a group
166 TPushParser::do_end()
168 TNode parent = cursor.parent();
169 if (parent && parent.isG() && parent.hasId())
171 // normal closing brace for an explicitly open group
176 else if (parent && parent.isG() && parent.parent() && parent.parent().is("cell"))
178 assert(!frames.empty());
179 // closing brace for a structure in which & or \cr have been used
180 TNode row = parent.parent().parent();
181 assert(row && row.is("row"));
182 assert(row.parent());
186 else if (parent && parent.isG() && !parent.hasId() && parent.parent() && !parent.parent().is("math"))
188 // In this case, we have to control the cursor's grand parent.
189 TNode gparent = parent.parent();
191 if (gparent.isC() && gparent.last() == parent)
193 // a frame MUST be in the stack
194 assert(!frames.empty());
196 // we have to control the nature of this macro
197 if (frames.top().entry.rightOpen)
199 // in this case, the '}' character is the proper way to exit from the phantom group, and
200 // in particular, this character means that the user wants to exit from the MACRO.
201 // A rightOpen MACRO MUST be descendant of a group with Id. This '}' is the closing brace of this
202 // group. So, we have to control if this group exists. This groyp could exist, but this MACRO could
203 // be another MACRO's child, so we have to control this last MACRO recursively. This recurive control
204 // is done by the correctBrace method.
207 // the '}' is not correct
208 logger.warning("nothing to close");
220 logger.warning("ignored closing brace");
226 // at the moment, a phantom group with the cursor inside can be a MACRO's child or a cell's child, and these cases
227 // are handled in other blocks of code.
228 logger.error("do_end: strange TML tree");
234 // In this case, there is a redundant '}', so we can ignore it and
236 logger.warning("There is so no corresponding'{'");
243 TPushParser::do_shift()
245 TNode parent = cursor.parent();
247 if (parent.is("tex"))
249 TNode math = doc.create("math", nextId++);
250 TNode g = doc.createG();
251 cursor.replace(math);
256 else if (parent.isG() && !parent.hasId() && parent.parent() && parent.parent().is("math"))
260 // there is something before the cursor, hence this is the
261 // closing math shift
262 if (parent.parent()["display"] != "1")
264 // one math shift is enough to close it
270 // we need two closing math shifts
271 //cursor.remove(); ??
272 parent.parent().append(cursor);
276 else if (parent.parent()["display"] != "1")
278 // there is nothing before the cursor, and the math is not
279 // in display mode, so this must be a double math shift
280 parent.parent()["display"] = "1";
285 parent.parent().append(cursor);
289 else if (parent.is("math"))
296 logger.warning("not allowed here");
302 TPushParser::do_align()
304 TNode parent = cursor.parent();
305 if (parent && parent.isG() && parent.hasId())
307 // alignment tab used for the first time inside a group
308 TNode row = doc.create("row");
309 TNode cell = doc.create("cell");
310 TNode g = doc.createG();
313 g.append(parent.first(), cursor);
316 else if (parent && parent.isG() && parent.parent().is("cell"))
318 // alignment tab used within a cell
319 TNode oldCell = parent.parent();
320 assert(oldCell && oldCell.is("cell"));
321 TNode row = oldCell.parent();
322 assert(row && row.is("row"));
323 TNode cell = doc.create("cell");
324 if (oldCell.next()) oldCell.next().insert(cell);
325 else row.append(cell);
326 TNode g = doc.createG();
333 logger.warning("alignment tab used outside matrix");
339 TPushParser::do_eol()
341 //if (cursor.parent()) cursor.remove();
342 logger.warning("ignored token");
347 TPushParser::do_parameter(const std::string& p)
349 logger.warning("ignored token");
354 TPushParser::do_subscript()
356 TNode parent = cursor.parent();
359 TNode prev = cursor.prev();
362 TNode elem = doc.create("sb", nextId++);
363 TNode g = doc.createG();
364 cursor.replace(elem);
371 TNode elem = doc.create("sb", nextId++);
378 else if (parent.isSb() && cursor == parent[1])
380 if (parent["under"] == "1")
382 logger.warning("already under");
387 parent["under"] = "1";
393 logger.warning("ignored token");
399 TPushParser::do_superscript()
401 TNode parent = cursor.parent();
404 TNode prev = cursor.prev();
407 TNode elem = doc.create("sp", nextId++);
408 TNode g = doc.createG();
409 cursor.replace(elem);
416 TNode elem = doc.create("sp", nextId++);
423 else if (parent.isSp() && cursor == parent[1])
425 if (parent["over"] == "1")
427 logger.warning("already over");
432 parent["over"] = "1";
438 logger.warning("ignored token");
444 TPushParser::do_ignorablespace(const std::string& s)
446 // At the moment, do nothing
450 TPushParser::do_space(const std::string&)
452 TNode elem = doc.createS(nextId++);
453 cursor.replace(elem);
459 TPushParser::do_letter(const std::string& s)
461 //TNode parent = cursor.parent();
462 TNode elem = doc.createI(s, nextId++);
463 cursor.replace(elem);
469 TPushParser::do_digit(const std::string& s)
471 TNode elem = doc.createN(s, nextId++);
472 cursor.replace(elem);
478 TPushParser::isPrimes(const TNode& node) const
481 return node.isG() && node.last() && node.last().is("o") && node.last()["name"] == "prime";
485 TPushParser::do_apostrophe()
487 if (cursor.parent() && cursor.parent().isG())
489 if (TNode prev = cursor.prev())
491 if (prev.isSp() && prev[1] && isPrimes(prev[1]))
493 prev[1].append(PRIME());
496 else if (prev.isSb() && prev[0] &&
497 prev[0].isSp() && prev[0][1] &&
498 isPrimes(prev[0][1]))
500 prev[0][1].append(PRIME());
505 TNode elem = doc.create("sp");
506 TNode g = doc.createG();
517 logger.warning("you have to insert an identifier before a ''");
523 logger.warning("cursor has to be in a group");
529 TPushParser::do_other(const std::string& s)
534 return do_apostrophe();
537 /*cout << "TPushParser::do_other " << s << endl;
538 cout << "DOCUMENT: " << static_cast<GdomeNode*>(cursor.element().get_ownerDocument()) << endl;*/
539 TNode elem = doc.createT("o", s, nextId++);
540 cursor.replace(elem);
548 TPushParser::do_active(const std::string&)
551 logger.warning("ignored token");
556 TPushParser::do_comment()
565 TNode parent = cursor.parent();
566 if (parent && parent.isG() &&
567 parent.parent() && parent.parent().is("cell") &&
568 parent.parent().parent() && parent.parent().parent().is("row"))
570 TNode oldRow = parent.parent().parent();
572 TNode table = oldRow.parent();
574 TNode row = doc.create("row");
575 TNode cell = doc.create("cell");
576 TNode g = doc.createG();
577 if (oldRow.next()) oldRow.next().insert(row);
578 else table.append(row);
586 // at the moment, \cr can only be used inside a table
587 logger.warning("cr used outside a table");
593 TPushParser::do_control(const std::string& name)
595 if (name == "cr") return do_cr();
598 TNode parent = cursor.parent();
599 const TDictionary::Entry& entry = dictionary.find(name);
602 case TDictionary::IDENTIFIER:
604 TNode t = doc.createI(entry.value, nextId++);
611 case TDictionary::OPERATOR:
613 TNode t = doc.createO(entry.value, nextId++);
620 case TDictionary::NUMBER:
622 TNode t = doc.createN(entry.value, nextId++);
629 case TDictionary::MACRO:
633 TNode m = doc.createC(name, nextId++);
635 if (entry.leftOpen && entry.rightOpen)
637 assert(entry.pattern.empty());
638 assert(parent.isG());
639 TNode g1 = doc.createG();
640 g1["left-open"] = "1";
641 g1.append(parent.first(), m);
643 TNode g2 = doc.createG();
646 frames.push(Frame(entry));
648 else if (entry.leftOpen)
650 assert(parent.isG());
651 TNode g = doc.createG();
652 g["left-open"] = "1";
653 g.append(parent.first(), m);
657 else if (entry.rightOpen)
659 assert(entry.pattern.empty());
660 assert(parent.isG());
661 TNode g = doc.createG();
664 frames.push(Frame(entry));
666 else if (!entry.pattern.empty())
668 frames.push(Frame(entry));
669 if (entry.paramDelimited(0))
671 TNode g = doc.createG();
680 // it's an empty macro
685 else if (!entry.pattern.size() && !entry.rightOpen && !entry.leftOpen)
687 // a macro with no arguments and no right open and no left open, can be child of anything
688 TNode m = doc.createC(name, nextId++);
695 // a macro with arguments or a rightOpen or leftOpen macro must be a group's child
696 logger.warning("ignored token: this macro should be in a group");
701 case TDictionary::UNDEFINED:
703 logger.warning("using undefined macro " + name);
704 TNode m = doc.createC(name, nextId++);
713 logger.warning("ignored token");
721 TPushParser::drop_prev_token(bool special)
723 assert(cursor.prev());
724 assert(cursor.parent());
725 TNode prev = cursor.prev();
728 DOM::UCS4String ucs4val = prev.element().getAttribute("val");
729 bool macro = prev.element().hasAttribute("name");
730 std::string utf8name;
731 if (macro) utf8name = prev.element().getAttribute("name");
734 prev.replace(cursor);
736 if (cursor.parent().isC())
738 // in this case we have removed an element of a MACRO.
739 // we can assert that this element was a non delimited argument
740 assert(!frames.empty());
741 Frame& frame = frames.top();
742 assert(frame.pos > 0);
746 if ((ucs4val.length() > 1))
750 // in this case we can return the content of ucs4val, but we have
751 // to convert it in a utf8
752 DOM::GdomeString gdsval(ucs4val);
753 std::string utf8val(gdsval);
754 switch (utf8val[utf8val.length() - 1])
758 return (special) ? std::string(utf8val, 0, utf8val.length() - 1) + "\\" : std::string(utf8val, 0, utf8val.length() - 1);
759 default: return (special) ? "" : std::string(utf8val, 0, utf8val.length() - 1);
764 // in this case, the content of val could be in unicode,
765 // but we have the attribute name, which doesn't contain character not representable
767 return (special) ? "\\" + utf8name : "";
770 else if (macro && special) return "\\" + utf8name;
775 TPushParser::drop_prev_script(bool special)
777 // this method deletes an sp or an sb preceding the cursor
778 assert(cursor.prev());
779 assert(cursor.parent());
780 TNode prev = cursor.prev();
781 assert(prev.is("sp") || prev.is("sb"));
784 // we can invoke the drop_prev, because a sp (sb) MUST have two children
785 // but we cannot invoke do_drop_script because it assumes when called, the first
786 // child has been removed yet.
787 if (cursor.prev().isG() && !prev.hasId())
789 // in this case, the user has inserted a sequence of '.
790 // Hence, we force a normal deletion, because the behavior must be the same
791 // for the two kind of deletion
792 return drop_prev(false);
794 else return drop_prev(special);
798 TPushParser::drop_prev_group(bool special)
800 assert(cursor.prev() && cursor.prev().isG());
801 TNode parent = cursor.parent();
802 TNode prev = cursor.prev();
806 if (parent.isC() && prev.hasId())
808 // this previous group is a macro's argument. Entering inside it means that
809 // this argument becomes incomplete. Hence, we have to decrement the member pos.
810 assert(!frames.empty());
814 if (special) return "";
817 // a group could have no children, so the drop_prev is not appropriate
818 // so, this method is not equivalent to the one above
819 return do_drop(special);
824 TPushParser::drop_prev_macro(bool special)
826 assert(cursor.parent());
827 assert(cursor.prev());
828 TNode prev = cursor.prev();
831 std::string macro_name = prev.nameC();
833 TNode parent = cursor.parent();
835 const TDictionary::Entry& entry = dictionary.find(prev["name"]);
837 if (!entry.defined())
839 // In this case, with a normal deletion, we completely remove the macro.
840 // With a special deletion, we remove the last character of the macro's name.
842 prev.replace(cursor);
843 if (cursor.parent().isC())
845 // we have removed a macro's child
846 assert(!frames.empty());
849 if (special) return "\\" + macro_name.erase(macro_name.length() - 1, 1); // we remove the last char, because an undefined macro's name is visible
854 // we start to remove a MACRO. Different actions must be taken, based on the nature
855 // of the MACRO. In some cases, we can't remove the MACRO immediately, in other
856 // cases it's correct. In the first set of cases, we have to update the stack, pushing
857 // a frame in it with a correct value of pos, in the
858 // second one, we must not push a frame in the stack
862 // In this fragment of code we also handle the leftOpen && rightOpen MACRO.
863 // since the control element is rightOpen, the cursor should be placed after
864 // the last child of the control element's last child, and than, we try to remove something.
865 // A frame MUST be pushed in the stack, because we dont' know if the following actions
866 // will completely remove the MACRO.
867 frames.push(Frame(entry));
869 // Since the MACRO is rightOpen, the last child of the MACRO must be a phantom group
870 assert(prev.last().isG() && !prev.last().hasId());
873 prev.last().append(cursor);
875 if (special) return "";
878 // the drop_prev is not appropriate, because the last child of the MACRO could have no children
879 return do_drop_phantom_group(special);
882 else if (entry.leftOpen)
884 // the leftOpen MACRO MUST have one and only one child, which MUST be a phantom group
885 // In this case, we do not have to push a frame in the stack, because we remove the
886 // MACRO immediately, substituting it with the content of the phantom group.
887 // We could remove the last child of the phantom group, but
888 // it's not clear if it's the correct behavior of the graphical deletion.
889 // At the moment, to give a standard behavior, we remove the last element.
890 // With a special deletion, we do not remove it.
891 assert(prev.first());
892 assert(prev.first().isG());
893 assert(prev.first() == prev.last());
895 TNode g = prev.first();
898 // in this case, the phantom group has at least one child, so we can call the
901 prev.replace(g.first(), TNode());
902 parent.append(cursor);
903 if (special) return "\\" + macro_name;
904 else return do_drop(special);
908 // otherwise, the phantom group has no children, so we remove it, also the MACRO.
911 prev.replace(cursor);
912 if (special) return "\\" + macro_name;
915 // Once removed this empty macro, we could try to remove something else.
916 // This would be justified by the fact that, generally, an empty macro gives no visual information
918 return do_drop(special); // special is false
922 else if (!entry.pattern.empty())
924 // we have to start to remove a MACRO which accepts arguments.
925 // If the MACRO accepts arguments, the MACRO has at least one child
926 assert(prev.size() >= 1);
928 // Differnt actions must be taken, based on the nature of the last child
929 // of the MACRO. We have to distinguish the case in which it's a delimited argument,
930 // frome the one in which it's a not delimited argument.
931 if (prev.last().isG() && !prev.last().hasId())
935 // in this case, we have to start removing the last delimiter
936 frames.push(Frame(entry, entry.pattern.size() - 2));
939 prev.last().append(cursor);
941 std::string last_del = entry.pattern[entry.pattern.size() - 1].value;
943 return "\\" + last_del;
947 // the last argument of the MACRO is a delimited argumet. We ideally remove
948 // the sequence of delimiters
950 prev.last().append(cursor);
951 // we have to push a frame with a correct value of pos
952 assert(entry.previousParam(entry.pattern.size()) != entry.pattern.size());
954 unsigned sequence_length = entry.pattern.size() - entry.previousParam(entry.pattern.size()) - 1;
955 unsigned p = entry.pattern.size() - sequence_length - 1;
956 // now, p is the correct value of pos, and we can push the frame.
957 frames.push(Frame(entry, p));
959 // To give a standard behavior to the graphical deletion, we remove the last
960 // element of the macro. Since we are in a phantom group, we can invoke the
961 // do_drop_phantom_group(special).
962 return do_drop_phantom_group(special);
967 // in this case, the last child of the MACRO is not a delimited argument, so we try
968 // to remove it, but we have to take differnt actions if the MACRO is a table with rows or not.
970 if (entry.table == 1 && prev.last().is("row"))
972 // in this case the cursor has to be appended to the group associated to
973 // the last cell of the last row of the table
974 assert(prev.last().last().is("cell") && prev.last().last().first().isG());
975 prev.last().last().first().append(cursor);
977 // we have to push a frame in the stack. Since tables has a pattern size = 1, we have to
978 // set pos at 0, because appending the cursor to the last cell means that this argument
979 // is not whole inserted.
980 // We don't call frames.push(Frame(entry)), because it incoditionaly set pos at 0. The
981 // following line is more general
982 frames.push(Frame(entry, entry.pattern.size() - 1));
985 // to type a table with rows and cells, the user had typed a
986 // "{", and to exit from it, the user had inserted a "}".
987 // Since we are in a special deletion, we just idealy remove the "}"
990 else return do_drop_phantom_group(special);
994 // we push a frame in the stack with a correct value of member pos.
995 // This correct value is the size of the pattern - 1, because we have been started to delete
996 // a MACRO. It means that all of the MACRO's arguments have been inserted, but
997 frames.push(Frame(entry, entry.pattern.size()));
999 return drop_prev(special);
1002 } // end of the else of the if (prev.last().isG() && !prev.last().hasId())
1004 } // end of if (!entry.pattern.empty())
1007 // if we are here, the MACRO preceding the cursor, is !(rightOpen || leftOpen),
1008 // and has no pattern. It means that it has no children.
1009 // We can replace it with the cursor
1010 assert(prev.size() == 0);
1012 prev.replace(cursor);
1013 if (cursor.parent().isC())
1015 // we have removed an empty macro, which was a non delimited argument of a macro.
1016 // We have to decrement pos
1017 assert(!frames.empty());
1021 if (special) return "\\" + macro_name;
1024 // now we could start to remove something else. This behavior would be justified by the
1025 // fact that, generally, an empty MACRO gives no visual information about it.
1026 // To adopt this behavior, just remove the comment to the following instruction
1027 // return do_drop(special);
1029 } // end of defined MACRO
1034 TPushParser::drop_prev(bool special)
1036 // if in this function, the prev of cursor does exist, also the parent and we want a graphical deletion.
1038 assert(cursor.prev());
1039 assert(cursor.parent());
1041 TNode prev = cursor.prev();
1045 return drop_prev_token(special);
1047 else if (prev.isSp() || prev.isSb())
1049 return drop_prev_script(special);
1051 else if (prev.isG())
1053 return drop_prev_group(special);
1055 else if (prev.isC())
1057 // here, we also treat the case in which the MACRO is a table
1058 return drop_prev_macro(special);
1062 // not handled. Future cases...
1069 TPushParser::rgreplace_father()
1071 // this method MUST only be invoked, when the cursor
1072 // is the only child of a group with id. This function
1073 // replaces the group with the cursor. But if the new parent
1074 // is a group with id and the cursor is the only child of the
1075 // group, the new parent is replaced...and so on.
1076 // r stands for recursive, g stands for graphical.
1077 assert(cursor.parent());
1078 assert(cursor.parent().isG() && cursor.parent().hasId());
1080 TNode parent = cursor.parent();
1082 while (parent.isG() && parent.hasId() && (parent.first() == cursor))
1084 parent.replace(cursor);
1085 parent = cursor.parent();
1090 TPushParser::do_drop_script(bool special)
1092 // If we are here, the cursor is child of a script (sp or sb) and
1093 // this means that a prev does exist and that there is one and only one
1094 // element preceding the cursor. The sp's (or sb's) parent
1095 // MUST NOT be a MACRO.
1096 // The element preceding the cursor is the base of the script.
1098 assert(cursor.parent() && (cursor.parent().isSp() || cursor.parent().isSb()));
1099 TNode parent = cursor.parent();
1101 assert(parent.size() == 2);
1102 assert(parent.parent() && !parent.parent().isC());
1104 TNode prev = cursor.prev();
1106 if (prev.isG() /*&& !prev.hasId()*/ && (prev.size() == 0))
1108 // in this case, the script's base is a group with no elements, so
1109 // we have to remove the entire MACRO, replacing it with the cursor.
1110 // This situation occurs when the user had typed something like this
1117 if (special && prev.hasId())
1119 // in this case, the user has typed: ...{}^
1120 // hence we idealy remove the ^
1121 parent.replace(prev);
1122 prev.parent().append(cursor);
1125 else if (!prev.hasId())
1127 // we idealy remove the ^, but the phantom group
1128 // has to be removed, also
1130 parent.replace(cursor);
1136 parent.replace(cursor);
1138 // since the script had no children, we can try to remove something else.
1139 // Since we don't know who is cursor's parent, and who is cursor's preceding
1140 // element, we invoke the do_drop()
1141 return do_drop(special);
1146 // in this case, the prev has to replace the script.
1147 parent.replace(prev);
1148 prev.parent().append(cursor);
1149 // now prev have a preceding element
1150 assert(cursor.parent().size() > 1);
1152 if (special) return "";
1155 // to give a standard behavior, we try to remove the element, which was
1156 // the script's base.
1157 return do_drop(special);
1161 } // end of method do_drop_script
1164 TPushParser::do_drop_macro(bool special)
1166 // If we are here, the cursor is a child of a MACRO and this means
1167 // that there is an open frame for the control element
1168 // and this element is closed at either side (no leftOpen no rightOpen)
1169 // and the MACRO is waiting for a not delimited argument, so
1170 // we can assert that frame.entry.pattern.size() >= 1
1171 assert(cursor.parent() && cursor.parent().isC());
1172 TNode parent = cursor.parent();
1174 // this string is useful iff we have a special deletion.
1175 std::string macro_name = parent.nameC();
1177 assert(!frames.empty());
1178 Frame& frame = frames.top();
1179 assert(frame.entry.pattern.size() >= 1);
1181 // we have to take different actions, based on if a preceding element exists
1183 TNode prev = cursor.prev();
1186 // in this case, a prev does not exist, so the actions of deleting means
1187 // that we have to remove the MACRO. So we have to pop the stack.
1188 assert(frame.pos == 0);
1190 parent.replace(cursor);
1193 if (special) return "\\" + macro_name;
1196 // Since the macro had no children and this is a graphical deletion, we try
1197 // to remove something else
1198 return do_drop(special);
1203 // a prev does exist, we have to control if it's a delimited argument or not.
1204 if (prev.isG() && !prev.hasId())
1206 // in this case, prev is a delimited argument, so we have
1207 // to ideally remove the sequence of delimiters
1208 Frame& frame = frames.top();
1209 assert(frame.pos > 1);
1211 prev.append(cursor);
1212 assert(frame.entry.previousParam(frame.pos) != frame.entry.pattern.size());
1216 // in this case we have to start removing the last delimimeter.
1217 // It means that we return in a situation where the user has not entirely
1218 // inserted the delimited argument. So, we have to decrement frame.pos of
1219 // two units: the delimiter and the actual argument
1220 std::string last_del = frame.entry.pattern[frame.pos - 1].value;
1221 frame.pos = frame.pos - 2;
1222 return "\\" + last_del;
1226 // these 3 lines of code update the member pos.
1227 unsigned sequence_length = frame.pos - frame.entry.previousParam(frame.pos) - 1;
1228 assert(sequence_length);
1229 frame.pos = frame.pos - sequence_length - 1;
1231 // since it's a graphical deletion, we have to remove the current preceding element.
1232 // We don't invoke the drop_prev(), because a do_drop_phantom_group is more general.
1233 return do_drop_phantom_group(special);
1238 // the prev is not a delimited argument, so we have to try to remove it.
1239 // We "try", because the prev might be something that
1240 // a simple deletion cannot remove completely
1241 return drop_prev(special);
1248 TPushParser::do_drop_groupId(bool special)
1250 // if we are here, the cursor's parent is a group with Id
1251 assert(cursor.parent() && cursor.parent().isG() && cursor.parent().hasId());
1252 TNode parent = cursor.parent();
1254 // we have to take different actions based on if the cursor has a preceding
1256 TNode prev = cursor.prev();
1259 // the cursor has a preceding element, so we try to remove it
1260 if (special) return drop_prev(special);
1263 std::string str = drop_prev(special);
1265 // We control if the group has to be removed, because the cursor
1266 // might be the only element of the group.
1267 // But we have to be careful, because drop_prev could change the TML tree
1268 // more than we think...parent could no longer exist!
1269 parent = cursor.parent();
1270 if ((parent.first() == cursor) && parent.isG() && parent.hasId())
1278 // the cursor has no preceding elements, so we have to remove the
1282 parent.replace(cursor);
1288 // we have to re-start the process, because it' a graphical deletion
1289 return do_drop(special);
1293 } // end of method do_drop_groupId()
1296 TPushParser::do_drop_phantom_group(bool special)
1298 // if we are here, the cursor MUST be a child of a
1300 assert(cursor.parent() && cursor.parent().isG() && !cursor.parent().hasId());
1302 TNode parent = cursor.parent();
1304 // now we have to control if the cursor has a preceding element or not
1305 TNode prev = cursor.prev();
1308 if (parent.parent() && parent.parent().isC())
1310 // there is a frame in the stack
1311 assert(!frames.empty());
1312 if (frames.top().entry.pattern.size())
1314 Frame& frame = frames.top();
1317 // we are in a delimited argument. If the user has inserted a proper subset of the
1318 // delimiters'sequence, we start to remove the previous delimiter. Start to remove
1319 // a delimiter means that that delimiter must be removed from the count of inserted delimiters.
1320 // It means that we have to decrement the member pos.
1321 if (frame.entry.pattern[frame.pos].category != TToken::PARAMETER)
1323 std::string del = frame.entry.pattern[frame.pos].value;
1330 // we are in a delimited argument. If the user has inserted a proper subset of the delimiters'sequence,
1331 // we have to remove the portion the user has inserted.
1332 while (frame.entry.pattern[frame.pos].category != TToken::PARAMETER) frame.pos--;
1337 // the cursor has a preceding element, so we try to remove it
1338 std::string str = drop_prev(special);
1340 if (special) return str;
1343 // now we have to control the parent, to handle the case of primes. But we have returned from a drop_prev(), which
1344 // could change the TML tree. So not asssuming that cursor's parent is unchanged is convenient.
1345 parent = cursor.parent();
1346 if (parent.isG() && !parent.hasId() && (parent.size() == 1) && parent.parent().isSp())
1348 // in this case the drop_prev has removed the only element preceding the cursor.
1349 // Since the phantom group is an sp's child, the user has removed all \' in the
1351 // Now we have some possibilities:
1352 // - we can replace the phantom group with the cursor, giving the user the chance to insert a new
1354 // - we can remove the phantom group and the sp element, recreating the state before the user inserted the first
1356 // At the moment we implement the second one.
1357 assert(parent.parent().size() == 2);
1358 TNode gparent = parent.parent();
1359 TNode base = gparent.first();
1362 gparent.replace(base);
1363 // now base's parent is no more gparent
1364 base.parent().append(cursor);
1368 else if (parent.isG() && !parent.hasId() && parent.parent().isSp())
1370 // in this case we have to place the cursor after the sp element
1372 assert(parent.parent().parent());
1373 parent.parent().parent().append(cursor);
1381 // in this case the cursor is the only element of the phantom group,
1382 // so we have to remove it. But, a phantom group has a special role,
1383 // so we have to control the grand father of the cursor.
1384 TNode gfather = parent.parent();
1387 // If here, the TML tree is in an inconsistent state
1388 logger.error("TML tree in a inconsistent state");
1391 else if (gfather.isC())
1393 // in this case the phantom group is child of a MACRO.
1394 // We have to control the nature of this MACRO.
1395 assert(!frames.empty());
1396 Frame& frame = frames.top();
1398 // this variable is useful in a special deletion
1399 std::string macro_name = gfather.nameC();
1401 if (frame.entry.leftOpen && frame.entry.rightOpen)
1403 // in this case, the cursor'parent is in the second and last child
1404 // of the MACRO. We can assert that the grand father has two
1405 // children, which are both phantom groups
1406 assert(gfather.size() == 2);
1407 assert((gfather.last() == parent) && (gfather.first().isG() && !gfather.first().hasId()));
1408 assert(frame.pos == 0);
1410 TNode ggfather = gfather.parent();
1414 // we have to replace the gfather with the elements of its first child, but this group may have no
1416 if (gfather.first().size())
1418 gfather.replace(gfather.first().first(), TNode());
1419 ggfather.append(cursor);
1423 // in this case, the MACRO has to be replaced with the cursor
1424 gfather.first().remove();
1425 gfather.replace(cursor);
1427 // now we have the situation preceding the insertion of the leftOpen and rightOpen MACRO.
1428 // this MACRO no longer exists.
1431 if (special) return "\\" + macro_name;
1434 // to give a standard behavior to the graphical deletion, we call the do_drop.
1435 return do_drop(special);
1438 else if (frame.entry.rightOpen)
1440 // the user has inserted a rightOpen MACRO, and now, this MACRO has no children (excluding the
1441 // phantom group), so we remove the MACRO.
1442 // We can assert that cursor's parent is the only child of the MACRO
1443 assert(gfather.size() == 1);
1444 assert(frame.pos == 0);
1447 gfather.replace(cursor);
1449 // now we have the situation preceding the rightOpen MACRO, so we have to pop the frame
1452 if (special) return "\\" + macro_name;
1455 // to give a standard behavior to the graphical deletion, we call the do_drop.
1456 return do_drop(special);
1460 else if (frame.entry.leftOpen)
1462 // this situation will never occur.
1463 logger.error("the parser has generated a wrong TML tree");
1466 else if (!frame.entry.pattern.empty())
1468 // the MACRO accepts arguments, and the phantom group in which
1469 // the cursor is, rappresents a delimited argument.
1470 // We have to control if the cursor's parent has a preceding element,
1472 TNode uncle = parent.prev();
1475 // the parent is the only element of the MACRO.
1476 // we can assert that frame.pos == 0.
1477 // In this case we can replace the MACRO with the cursor
1478 assert(frame.pos == 0);
1481 gfather.replace(cursor);
1484 if (special) return "\\" + macro_name;
1487 // once we have replaced the empty macro with the cursor, we can remove
1489 return do_drop(special);
1494 // the parent has a preceding element. Now we have
1495 // to control if the uncle is a delimited argument or not.
1496 if (uncle.isG() && !uncle.hasId())
1498 // cursor's uncle is a delimited argument
1501 uncle.append(cursor);
1504 // we have to start removing the last delimiter of the delimited
1506 std::string last_del = frame.entry.pattern[frame.pos - 1].value;
1507 frame.pos = frame.pos - 2;
1508 return "\\" + last_del;
1512 // the uncle is a delimited argument. So we have to ideally
1513 // remove the sequence of delimiters.
1514 assert(frame.pos > 1);
1515 unsigned sequence_length = frame.pos - frame.entry.previousParam(frame.pos) - 1;
1516 assert(frame.entry.previousParam(frame.pos) != frame.entry.pattern.size());
1517 assert(sequence_length);
1518 // sequence_length is the length of the delimiters sequence which separates
1519 // the current parameter and the previous parameter
1520 frame.pos = frame.pos - sequence_length - 1;
1522 // once removed the sequnce of delimiters, we can start to remove the actual
1523 // parameter. We can call the do_drop_phantom_group() because a delimited argument
1524 // is always a phantom group's child
1525 return do_drop_phantom_group(special);
1530 // the uncle is a not delimited argument, so we try to remove it.
1532 parent.replace(cursor);
1533 parent = cursor.parent(); // we update the parent (it should be the MACRO)
1534 assert(parent.isC());
1536 // now we try to remove the uncle (now it' the preceding element)
1537 return drop_prev(special);
1539 } // this is the else's end, that handles the case in which an uncle exists
1540 } // end of if (!frame.entry.pattern.empty())
1543 // the entry has no arguments, is not rightOpen and is not leftOpen.
1544 logger.error("TML tree in a strange state");
1547 } // end of if (gfather.isC())
1548 else if (gfather.is("cell"))
1550 // A table is a control sequence, so there is a frame in the stack
1551 assert(!frames.empty());
1552 assert(frames.top().pos == 0);
1553 assert(frames.top().entry.table == 1);
1555 // a cell MUST be a row's child, which in turn is a table's child
1556 assert(gfather.parent() && gfather.parent().is("row") && gfather.parent().parent());
1558 // this variable is useful to handle the special deletion
1559 std::string table_name = gfather.parent().parent().nameC();
1561 TNode row = gfather.parent();
1563 // in this case the cell has no element, so the user wants to delete this cell.
1564 TNode prev_cell = gfather.prev();
1568 // now the cell no longer exists
1572 // in this case, the cell was the only cell in the row.
1573 // So, we assume that the user wants to delete the entire row.
1574 TNode table = row.parent();
1575 TNode prev_row = row.prev();
1582 // Since there was a cell (and a row), the user has typed a "{" to
1583 // We ideally remove this character.
1584 table.append(cursor);
1589 // the row was the only child of the table.
1590 // so we have to delete the entire table
1591 assert(table.parent());
1592 TNode parent_table = table.parent();
1595 parent_table.append(cursor);
1601 // there are other rows (one or more)
1602 assert(prev_row.is("row"));
1603 assert(prev_row.last());
1604 TNode last_cell = prev_row.last();
1605 assert(last_cell.is("cell"));
1606 assert(last_cell.size() == 1);
1607 assert(last_cell.first().isG() && !last_cell.first().hasId());
1608 last_cell.first().append(cursor);
1609 // Since cells and rows are separated by spaces and CRs
1610 // (and the user can see this spaces and CRs), a special deletion
1611 // is equivalent to a normal deletion
1614 } // end of if (!prev_cell)
1617 // being here means that there is a previous cell,
1618 // so we append the cursor to group.
1619 assert(prev_cell.size() == 1);
1620 assert(prev_cell.first().isG() && !prev_cell.first().hasId());
1621 prev_cell.first().append(cursor);
1624 } // end of if (gfather.is("cell"))
1625 else if (gfather.isSp())
1627 // we cannot be here because a phantom group can be a Sp child only
1628 // in two cases. If the user has typed somethong like:
1630 // the cursor is not phantom group's child.
1631 // If the user has typed somethong like
1633 // In this case the sequence of ' is placed in a phantom group,
1634 // which becomes the exponent of the script. But, the cursor is
1635 // always outside the phantom group
1636 logger.error("TML tree in a strange state");
1639 else if (gfather.is("math"))
1641 // in this case we ignore the user's will of deleting
1642 // but we could also decide to remove the math mode.
1643 logger.warning("nothing to delete");
1648 // cursor's grand father is undefined
1649 logger.error("TML tree is in an unknown state");
1652 } // end of the else of the if (prev)
1658 TPushParser::do_drop(bool special)
1660 // we have to handle the case in wich the cursor has a parent or not
1661 if (!cursor.parent())
1663 // it's not a good situation...at the moment we do not take actions
1664 logger.error("TML tree not well structured");
1669 // a parent exists. We have to take differnt actions, based on the nature of
1671 TNode parent = cursor.parent();
1672 if (parent.is("math"))
1674 // we ca do two thing...we can remove the math mode (it implies controlling the display attribute), we can do nothing
1675 // At the moment, the user's will of deleting is simply ignored
1676 logger.warning("nothing to delete");
1679 else if (parent.isG())
1681 // the cursor's parent is a group. We have to control if it's a phantom group or not
1684 return do_drop_groupId(special);
1688 return do_drop_phantom_group(special);
1690 } // end of parent is group
1691 else if (parent.isC())
1693 return do_drop_macro(special);
1694 } // end of parent is a MACRO
1695 else if (parent.isSp() || parent.isSb())
1697 return do_drop_script(special);
1698 } // end of parent is sp or sb
1699 } // end of the else which consider the case in which parent exists
1701 } // end of method do_drop
1704 TPushParser::process(const TToken& token)
1706 switch (token.category)
1708 case TToken::BEGIN: return do_begin();
1709 case TToken::END: return do_end();
1710 case TToken::SHIFT: return do_shift();
1711 case TToken::ALIGN: return do_align();
1712 case TToken::EOL: return do_eol();
1713 case TToken::PARAMETER: return do_parameter(token.value);
1714 case TToken::SUPERSCRIPT: return do_superscript();
1715 case TToken::SUBSCRIPT: return do_subscript();
1716 case TToken::IGNORABLE_SPACE: return do_ignorablespace(token.value);
1717 case TToken::SPACE: return do_space(token.value);
1718 case TToken::LETTER: return do_letter(token.value);
1719 case TToken::DIGIT: return do_digit(token.value);
1720 case TToken::OTHER: return do_other(token.value);
1721 case TToken::ACTIVE: return do_active(token.value);
1722 case TToken::COMMENT: return do_comment();
1723 case TToken::CONTROL: return do_control(token.value);
1728 TPushParser::push(const TToken& token)
1730 TNode parent = cursor.parent();
1731 // If the cursor has no parent then it is detached from the editing
1732 // tree, which means this token will be ignored
1735 // If the parent is a phantom group and the grand-parent is a
1736 // control sequence, there are two cases:
1737 // a. we are parsing a delimited argument of a entry
1738 // b. we are parsing a side of a right- or left-open entry
1739 if (parent.isG() && !parent.hasId() && parent.parent().isC())
1741 // There must be an open frame, for the grand-parent is a control sequence
1742 assert(!frames.empty());
1743 Frame& frame = frames.top();
1744 if (!frame.entry.pattern.empty())
1746 // The entry pattern is not empty. By our conventions this means
1747 // the entry cannot be open at either end, hence we are parsing
1748 // a delimited argument
1749 assert(frame.pos + 1 < frame.entry.pattern.size());
1750 assert(frame.entry.pattern[frame.pos + 1].category != TToken::PARAMETER);
1751 if (frame.entry.pattern[frame.pos + 1] == token)
1753 // The token matches with a delimiter of the argument,
1754 // hence we increment the frame.pos
1757 if (frame.entry.lastDelimiter(frame.pos))
1759 // this delimiter is the last one for the argumet,
1760 // so the argument is completed
1767 // Delimiter mismatch.
1768 if (frame.entry.pattern[frame.pos].category != TToken::PARAMETER)
1770 // in this case, there is a sequence of delimiters that delimitates
1771 // the argument, and the user has correctly inserted a portion of this
1772 // sequence, but now has inserted a wrong delimiter.
1773 // Here, there are some possibilities:
1774 // - ignore the token, and wait for the correct delimiter
1775 // - ignore the token, wait for the correct delimiter and emit an error
1776 // At the moment, we implement the second possibily
1777 logger.warning("it's not the correct delimiter...you have to type '" + frame.entry.pattern[frame.pos + 1].value + "'");
1781 // in this case, the sequence of delimiters is composed of one
1782 // delimiter. It means that we have to process the token
1789 // The entry pattern is empty, hence we are parsing a right-open
1790 // entry. What happens if we actually are in the left side?
1791 // This could happen only when re-editing an entered expression
1793 assert(frame.entry.rightOpen);
1797 else if (parent.isC())
1799 // We are parsing a non-delimited argument entry
1801 Frame& frame = frames.top();
1802 assert(frame.pos < frame.entry.pattern.size());
1804 if (frame.entry.pattern[frame.pos].category == TToken::PARAMETER)
1806 // As by the TeX parsing rules of undelimited parameters,
1807 // empty spaces are ignored
1808 if ((token.category != TToken::SPACE) && (token.category != TToken::IGNORABLE_SPACE)) process(token);
1810 else if (frame.entry.pattern[frame.pos] == token)
1812 // The token has been accepted
1814 if (frame.pos < frame.entry.pattern.size() &&
1815 frame.entry.paramDelimited(frame.pos))
1817 // If the next is a delimited argument we have to place
1818 // the phantom group with the cursor inside
1819 TNode g = doc.createG();
1831 // There is a mismatch. Emit an error and ignore the token?
1832 logger.warning("ignored token: " + token.value);
1839 logger.warning("ignored token");
1842 if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc);
1846 TPushParser::drop(bool special)
1848 std::string str = do_drop(special);
1849 if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc);
1854 TPushParser::advance(const TNode& node)
1861 logger.error("wrong TML tree");
1863 else if (node.parent().isG())
1865 TNode next = node.next();
1866 if (next) next.insert(cursor);
1867 else node.parent().append(cursor);
1869 else if (node.parent().isC())
1871 assert(!frames.empty());
1872 if ((frames.top().pos + 1 == frames.top().entry.pattern.size()) || (frames.top().entry.pattern.empty()))
1874 // we are here when we have a right open macro, or the inserted element is the last one
1875 if (frames.top().entry.rightOpen)
1877 // we have to remove the frame from the stack
1879 advance(node.parent().parent());
1884 advance(node.parent());
1887 else if (frames.top().entry.paramDelimited(frames.top().pos + 1))
1889 // the next argument is delimited, so we have to create a phantom group
1890 TNode g = doc.createG();
1892 node.parent().append(g);
1897 // the next argumet is not delimited, so we have to append the cursor
1899 node.parent().append(cursor);
1903 else advance(node.parent());
1907 TPushParser::setCursorHint(const std::string& c)
1909 if (cursor["val"] != c)
1912 if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc);
1917 TPushParser::hideCursor()
1919 if (hiddenCursor++ == 0)
1921 cursor["visible"] = "0";
1922 if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc);
1930 TPushParser::showCursor()
1932 if (hiddenCursor > 0 && --hiddenCursor == 0)
1934 cursor["visible"] = "1";
1935 if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc);
1945 if (APushParser::thaw() && factory && doc.dirtyNode())
1947 factory->documentModified(doc);