+TPushParser::rgreplace_father()
+{
+ // this method MUST only be invoked, when the cursor
+ // is the only child of a group with id. This function
+ // replaces the group with the cursor. But if the new parent
+ // is a group with id and the cursor is the only child of the
+ // group, the new parent is replaced...and so on.
+ // r stands for recursive, g stands for graphical.
+ assert(cursor.parent());
+ assert(cursor.parent().isG() && cursor.parent().hasId());
+
+ TNode parent = cursor.parent();
+
+ while (parent.isG() && parent.hasId() && (parent.first() == cursor))
+ {
+ parent.replace(cursor);
+ parent = cursor.parent();
+ }
+}
+
+std::string
+TPushParser::do_drop_script(bool special)
+{
+ // If we are here, the cursor is child of a script (sp or sb) and
+ // this means that a prev does exist and that there is one and only one
+ // element preceding the cursor. The sp's (or sb's) parent
+ // MUST NOT be a MACRO.
+ // The element preceding the cursor is the base of the script.
+
+ assert(cursor.parent() && (cursor.parent().isSp() || cursor.parent().isSb()));
+ TNode parent = cursor.parent();
+
+ assert(parent.size() == 2);
+ assert(parent.parent() && !parent.parent().isC());
+
+ TNode prev = cursor.prev();
+ cursor.remove();
+ if (prev.isG() /*&& !prev.hasId()*/ && (prev.size() == 0))
+ {
+ // in this case, the script's base is a group with no elements, so
+ // we have to remove the entire MACRO, replacing it with the cursor.
+ // This situation occurs when the user had typed something like this
+ // $....{}^
+ // or this
+ // $^
+ // or this
+ // $...{^
+ //
+ if (special && prev.hasId())
+ {
+ // in this case, the user has typed: ...{}^
+ // hence we idealy remove the ^
+ parent.replace(prev);
+ prev.parent().append(cursor);
+ return "";
+ }
+ else if (!prev.hasId())
+ {
+ // we idealy remove the ^, but the phantom group
+ // has to be removed, also
+ prev.remove();
+ parent.replace(cursor);
+ return "";
+ }
+ else
+ {
+ prev.remove();
+ parent.replace(cursor);
+
+ // since the script had no children, we can try to remove something else.
+ // Since we don't know who is cursor's parent, and who is cursor's preceding
+ // element, we invoke the do_drop()
+ return do_drop(special);
+ }
+ }
+ else
+ {
+ // in this case, the prev has to replace the script.
+ parent.replace(prev);
+ prev.parent().append(cursor);
+ // now prev have a preceding element
+ assert(cursor.parent().size() > 1);
+
+ if (special) return "";
+ else
+ {
+ // to give a standard behavior, we try to remove the element, which was
+ // the script's base.
+ return do_drop(special);
+ }
+ }
+
+} // end of method do_drop_script
+
+std::string
+TPushParser::do_drop_macro(bool special)
+{
+ // If we are here, the cursor is a child of a MACRO and this means
+ // that there is an open frame for the control element
+ // and this element is closed at either side (no leftOpen no rightOpen)
+ // and the MACRO is waiting for a not delimited argument, so
+ // we can assert that frame.entry.pattern.size() >= 1
+ assert(cursor.parent() && cursor.parent().isC());
+ TNode parent = cursor.parent();
+
+ // this string is useful iff we have a special deletion.
+ std::string macro_name = parent.nameC();
+
+ assert(!frames.empty());
+ Frame& frame = frames.top();
+ assert(frame.entry.pattern.size() >= 1);
+
+ // we have to take different actions, based on if a preceding element exists
+ // or not
+ TNode prev = cursor.prev();
+ if (!prev)
+ {
+ // in this case, a prev does not exist, so the actions of deleting means
+ // that we have to remove the MACRO. So we have to pop the stack.
+ assert(frame.pos == 0);
+
+ parent.replace(cursor);
+ frames.pop();
+
+ if (special) return "\\" + macro_name;
+ else
+ {
+ // Since the macro had no children and this is a graphical deletion, we try
+ // to remove something else
+ return do_drop(special);
+ }
+ }
+ else
+ {
+ // a prev does exist, we have to control if it's a delimited argument or not.
+ if (prev.isG() && !prev.hasId())
+ {
+ // in this case, prev is a delimited argument, so we have
+ // to ideally remove the sequence of delimiters
+ Frame& frame = frames.top();
+ assert(frame.pos > 1);
+ cursor.remove();
+ prev.append(cursor);
+ assert(frame.entry.previousParam(frame.pos) != frame.entry.pattern.size());
+
+ if (special)
+ {
+ // in this case we have to start removing the last delimimeter.
+ // It means that we return in a situation where the user has not entirely
+ // inserted the delimited argument. So, we have to decrement frame.pos of
+ // two units: the delimiter and the actual argument
+ std::string last_del = frame.entry.pattern[frame.pos - 1].value;
+ frame.pos = frame.pos - 2;
+ return "\\" + last_del;
+ }
+ else
+ {
+ // these 3 lines of code update the member pos.
+ unsigned sequence_length = frame.pos - frame.entry.previousParam(frame.pos) - 1;
+ assert(sequence_length);
+ frame.pos = frame.pos - sequence_length - 1;
+
+ // since it's a graphical deletion, we have to remove the current preceding element.
+ // We don't invoke the drop_prev(), because a do_drop_phantom_group is more general.
+ return do_drop_phantom_group(special);
+ }
+ }
+ else
+ {
+ // the prev is not a delimited argument, so we have to try to remove it.
+ // We "try", because the prev might be something that
+ // a simple deletion cannot remove completely
+ return drop_prev(special);
+ }
+ }
+
+}
+
+std::string
+TPushParser::do_drop_groupId(bool special)
+{
+ // if we are here, the cursor's parent is a group with Id
+ assert(cursor.parent() && cursor.parent().isG() && cursor.parent().hasId());
+ TNode parent = cursor.parent();
+
+ // we have to take different actions based on if the cursor has a preceding
+ // element or not
+ TNode prev = cursor.prev();
+ if (prev)
+ {
+ // the cursor has a preceding element, so we try to remove it
+ if (special) return drop_prev(special);
+ else
+ {
+ std::string str = drop_prev(special);
+
+ // We control if the group has to be removed, because the cursor
+ // might be the only element of the group.
+ // But we have to be careful, because drop_prev could change the TML tree
+ // more than we think...parent could no longer exist!
+ parent = cursor.parent();
+ if ((parent.first() == cursor) && parent.isG() && parent.hasId())
+ rgreplace_father();
+
+ return str;
+ }
+ }
+ else
+ {
+ // the cursor has no preceding elements, so we have to remove the
+ // group.
+ if (special)
+ {
+ parent.replace(cursor);
+ return "";
+ }
+ else
+ {
+ rgreplace_father();
+ // we have to re-start the process, because it' a graphical deletion
+ return do_drop(special);
+ }
+ }
+
+} // end of method do_drop_groupId()
+
+std::string
+TPushParser::do_drop_phantom_group(bool special)
+{
+ // if we are here, the cursor MUST be a child of a
+ // phantom group.
+ assert(cursor.parent() && cursor.parent().isG() && !cursor.parent().hasId());
+
+ TNode parent = cursor.parent();
+
+ // now we have to control if the cursor has a preceding element or not
+ TNode prev = cursor.prev();
+ if (prev)
+ {
+ if (parent.parent() && parent.parent().isC())
+ {
+ // there is a frame in the stack
+ assert(!frames.empty());
+ if (frames.top().entry.pattern.size())
+ {
+ Frame& frame = frames.top();
+ if (special)
+ {
+ // we are in a delimited argument. If the user has inserted a proper subset of the
+ // delimiters'sequence, we start to remove the previous delimiter. Start to remove
+ // a delimiter means that that delimiter must be removed from the count of inserted delimiters.
+ // It means that we have to decrement the member pos.
+ if (frame.entry.pattern[frame.pos].category != TToken::PARAMETER)
+ {
+ std::string del = frame.entry.pattern[frame.pos].value;
+ frame.pos--;
+ return "\\" + del;
+ }
+ }
+ else
+ {
+ // we are in a delimited argument. If the user has inserted a proper subset of the delimiters'sequence,
+ // we have to remove the portion the user has inserted.
+ while (frame.entry.pattern[frame.pos].category != TToken::PARAMETER) frame.pos--;
+ }
+ }
+ }
+
+ // the cursor has a preceding element, so we try to remove it
+ std::string str = drop_prev(special);
+
+ if (special) return str;
+ else
+ {
+ // now we have to control the parent, to handle the case of primes. But we have returned from a drop_prev(), which
+ // could change the TML tree. So not asssuming that cursor's parent is unchanged is convenient.
+ parent = cursor.parent();
+ if (parent.isG() && !parent.hasId() && (parent.size() == 1) && parent.parent().isSp())
+ {
+ // in this case the drop_prev has removed the only element preceding the cursor.
+ // Since the phantom group is an sp's child, the user has removed all \' in the
+ // phantom group.
+ // Now we have some possibilities:
+ // - we can replace the phantom group with the cursor, giving the user the chance to insert a new
+ // exponent
+ // - we can remove the phantom group and the sp element, recreating the state before the user inserted the first
+ // prime.
+ // At the moment we implement the second one.
+ assert(parent.parent().size() == 2);
+ TNode gparent = parent.parent();
+ TNode base = gparent.first();
+ cursor.remove();
+ parent.remove();
+ gparent.replace(base);
+ // now base's parent is no more gparent
+ base.parent().append(cursor);
+
+ return str;
+ }
+ else if (parent.isG() && !parent.hasId() && parent.parent().isSp())
+ {
+ // in this case we have to place the cursor after the sp element
+ cursor.remove();
+ assert(parent.parent().parent());
+ parent.parent().parent().append(cursor);
+ return str;
+ }
+ else return str;
+ }
+ }
+ else
+ {
+ // in this case the cursor is the only element of the phantom group,
+ // so we have to remove it. But, a phantom group has a special role,
+ // so we have to control the grand father of the cursor.
+ TNode gfather = parent.parent();
+ if (!gfather)
+ {
+ // If here, the TML tree is in an inconsistent state
+ logger.error("TML tree in a inconsistent state");
+ return "";
+ }
+ else if (gfather.isC())
+ {
+ // in this case the phantom group is child of a MACRO.
+ // We have to control the nature of this MACRO.
+ assert(!frames.empty());
+ Frame& frame = frames.top();
+
+ // this variable is useful in a special deletion
+ std::string macro_name = gfather.nameC();
+
+ if (frame.entry.leftOpen && frame.entry.rightOpen)
+ {
+ // in this case, the cursor'parent is in the second and last child
+ // of the MACRO. We can assert that the grand father has two
+ // children, which are both phantom groups
+ assert(gfather.size() == 2);
+ assert((gfather.last() == parent) && (gfather.first().isG() && !gfather.first().hasId()));
+ assert(frame.pos == 0);
+
+ TNode ggfather = gfather.parent();
+ assert(ggfather);
+ cursor.remove();
+ parent.remove();
+ // we have to replace the gfather with the elements of its first child, but this group may have no
+ // children.
+ if (gfather.first().size())
+ {
+ gfather.replace(gfather.first().first(), TNode());
+ ggfather.append(cursor);
+ }
+ else
+ {
+ // in this case, the MACRO has to be replaced with the cursor
+ gfather.first().remove();
+ gfather.replace(cursor);
+ }
+ // now we have the situation preceding the insertion of the leftOpen and rightOpen MACRO.
+ // this MACRO no longer exists.
+ frames.pop();
+
+ if (special) return "\\" + macro_name;
+ else
+ {
+ // to give a standard behavior to the graphical deletion, we call the do_drop.
+ return do_drop(special);
+ }
+ }
+ else if (frame.entry.rightOpen)
+ {
+ // the user has inserted a rightOpen MACRO, and now, this MACRO has no children (excluding the
+ // phantom group), so we remove the MACRO.
+ // We can assert that cursor's parent is the only child of the MACRO
+ assert(gfather.size() == 1);
+ assert(frame.pos == 0);
+ cursor.remove();
+ parent.remove();
+ gfather.replace(cursor);
+
+ // now we have the situation preceding the rightOpen MACRO, so we have to pop the frame
+ frames.pop();
+
+ if (special) return "\\" + macro_name;
+ else
+ {
+ // to give a standard behavior to the graphical deletion, we call the do_drop.
+ return do_drop(special);
+ }
+
+ }
+ else if (frame.entry.leftOpen)
+ {
+ // this situation will never occur.
+ logger.error("the parser has generated a wrong TML tree");
+ return "";
+ }
+ else if (!frame.entry.pattern.empty())
+ {
+ // the MACRO accepts arguments, and the phantom group in which
+ // the cursor is, rappresents a delimited argument.
+ // We have to control if the cursor's parent has a preceding element,
+ // or not.
+ TNode uncle = parent.prev();
+ if (!uncle)
+ {
+ // the parent is the only element of the MACRO.
+ // we can assert that frame.pos == 0.
+ // In this case we can replace the MACRO with the cursor
+ assert(frame.pos == 0);
+ cursor.remove();
+ parent.remove();
+ gfather.replace(cursor);
+ frames.pop();
+
+ if (special) return "\\" + macro_name;
+ else
+ {
+ // once we have replaced the empty macro with the cursor, we can remove
+ // something else
+ return do_drop(special);
+ }
+ }
+ else
+ {
+ // the parent has a preceding element. Now we have
+ // to control if the uncle is a delimited argument or not.
+ if (uncle.isG() && !uncle.hasId())
+ {
+ // cursor's uncle is a delimited argument
+ cursor.remove();
+ parent.remove();
+ uncle.append(cursor);
+ if (special)
+ {
+ // we have to start removing the last delimiter of the delimited
+ // argument.
+ std::string last_del = frame.entry.pattern[frame.pos - 1].value;
+ frame.pos = frame.pos - 2;
+ return "\\" + last_del;
+ }
+ else
+ {
+ // the uncle is a delimited argument. So we have to ideally
+ // remove the sequence of delimiters.
+ assert(frame.pos > 1);
+ unsigned sequence_length = frame.pos - frame.entry.previousParam(frame.pos) - 1;
+ assert(frame.entry.previousParam(frame.pos) != frame.entry.pattern.size());
+ assert(sequence_length);
+ // sequence_length is the length of the delimiters sequence which separates
+ // the current parameter and the previous parameter
+ frame.pos = frame.pos - sequence_length - 1;
+
+ // once removed the sequnce of delimiters, we can start to remove the actual
+ // parameter. We can call the do_drop_phantom_group() because a delimited argument
+ // is always a phantom group's child
+ return do_drop_phantom_group(special);
+ }
+ }
+ else
+ {
+ // the uncle is a not delimited argument, so we try to remove it.
+ cursor.remove();
+ parent.replace(cursor);
+ parent = cursor.parent(); // we update the parent (it should be the MACRO)
+ assert(parent.isC());
+
+ // now we try to remove the uncle (now it' the preceding element)
+ return drop_prev(special);
+ }
+ } // this is the else's end, that handles the case in which an uncle exists
+ } // end of if (!frame.entry.pattern.empty())
+ else
+ {
+ // the entry has no arguments, is not rightOpen and is not leftOpen.
+ logger.error("TML tree in a strange state");
+ return "";
+ }
+ } // end of if (gfather.isC())
+ else if (gfather.is("cell"))
+ {
+ // A table is a control sequence, so there is a frame in the stack
+ assert(!frames.empty());
+ assert(frames.top().pos == 0);
+ assert(frames.top().entry.table == 1);
+
+ // a cell MUST be a row's child, which in turn is a table's child
+ assert(gfather.parent() && gfather.parent().is("row") && gfather.parent().parent());
+
+ // this variable is useful to handle the special deletion
+ std::string table_name = gfather.parent().parent().nameC();
+
+ TNode row = gfather.parent();
+
+ // in this case the cell has no element, so the user wants to delete this cell.
+ TNode prev_cell = gfather.prev();
+ cursor.remove();
+ parent.remove();
+ gfather.remove();
+ // now the cell no longer exists
+
+ if (!prev_cell)
+ {
+ // in this case, the cell was the only cell in the row.
+ // So, we assume that the user wants to delete the entire row.
+ TNode table = row.parent();
+ TNode prev_row = row.prev();
+ row.remove();
+
+ if (!prev_row)
+ {
+ if (special)
+ {
+ // Since there was a cell (and a row), the user has typed a "{" to
+ // We ideally remove this character.
+ table.append(cursor);
+ return "";
+ }
+ else
+ {
+ // the row was the only child of the table.
+ // so we have to delete the entire table
+ assert(table.parent());
+ TNode parent_table = table.parent();
+ table.remove();
+ frames.pop();
+ parent_table.append(cursor);
+ return "";
+ }
+ }
+ else
+ {
+ // there are other rows (one or more)
+ assert(prev_row.is("row"));
+ assert(prev_row.last());
+ TNode last_cell = prev_row.last();
+ assert(last_cell.is("cell"));
+ assert(last_cell.size() == 1);
+ assert(last_cell.first().isG() && !last_cell.first().hasId());
+ last_cell.first().append(cursor);
+ // Since cells and rows are separated by spaces and CRs
+ // (and the user can see this spaces and CRs), a special deletion
+ // is equivalent to a normal deletion
+ return "";
+ }
+ } // end of if (!prev_cell)
+ else
+ {
+ // being here means that there is a previous cell,
+ // so we append the cursor to group.
+ assert(prev_cell.size() == 1);
+ assert(prev_cell.first().isG() && !prev_cell.first().hasId());
+ prev_cell.first().append(cursor);
+ return "";
+ }
+ } // end of if (gfather.is("cell"))
+ else if (gfather.isSp())
+ {
+ // we cannot be here because a phantom group can be a Sp child only
+ // in two cases. If the user has typed somethong like:
+ // $^
+ // the cursor is not phantom group's child.
+ // If the user has typed somethong like
+ // ..''
+ // In this case the sequence of ' is placed in a phantom group,
+ // which becomes the exponent of the script. But, the cursor is
+ // always outside the phantom group
+ logger.error("TML tree in a strange state");
+ return "";
+ }
+ else if (gfather.is("math"))
+ {
+ // in this case we ignore the user's will of deleting
+ // but we could also decide to remove the math mode.
+ logger.warning("nothing to delete");
+ return "";
+ }
+ else
+ {
+ // cursor's grand father is undefined
+ logger.error("TML tree is in an unknown state");
+ return "";
+ }
+ } // end of the else of the if (prev)
+
+}
+
+
+std::string
+TPushParser::do_drop(bool special)
+{
+ // we have to handle the case in wich the cursor has a parent or not
+ if (!cursor.parent())
+ {
+ // it's not a good situation...at the moment we do not take actions
+ logger.error("TML tree not well structured");
+ return "";
+ }
+ else
+ {
+ // a parent exists. We have to take differnt actions, based on the nature of
+ // the parent
+ TNode parent = cursor.parent();
+ if (parent.is("math"))
+ {
+ // we ca do two thing...we can remove the math mode (it implies controlling the display attribute), we can do nothing
+ // At the moment, the user's will of deleting is simply ignored
+ logger.warning("nothing to delete");
+ return "";
+ }
+ else if (parent.isG())
+ {
+ // the cursor's parent is a group. We have to control if it's a phantom group or not
+ if (parent.hasId())
+ {
+ return do_drop_groupId(special);
+ }
+ else
+ {
+ return do_drop_phantom_group(special);
+ }
+ } // end of parent is group
+ else if (parent.isC())
+ {
+ return do_drop_macro(special);
+ } // end of parent is a MACRO
+ else if (parent.isSp() || parent.isSb())
+ {
+ return do_drop_script(special);
+ } // end of parent is sp or sb
+ } // end of the else which consider the case in which parent exists
+
+} // end of method do_drop
+
+bool