X-Git-Url: http://matita.cs.unibo.it/gitweb/?a=blobdiff_plain;f=helm%2FDEVEL%2Fmathml_editor%2Fsrc%2FTPushParser.cc;h=a3fb9772996a11becbd03e578b62b6badac40ca2;hb=1957a1f151c547f04625c4379e51ab66bddb69c7;hp=cfcce849f887bd850c8669e09005cda3d860b518;hpb=e290ab0a049ec2babedc3cb0ff802c430ed8ee7c;p=helm.git diff --git a/helm/DEVEL/mathml_editor/src/TPushParser.cc b/helm/DEVEL/mathml_editor/src/TPushParser.cc index cfcce849f..a3fb97729 100644 --- a/helm/DEVEL/mathml_editor/src/TPushParser.cc +++ b/helm/DEVEL/mathml_editor/src/TPushParser.cc @@ -1,29 +1,40 @@ +#include "ALogger.hh" #include "TPushParser.hh" -#include "TListener.hh" +#include "AMathMLFactory.hh" -TPushParser::TPushParser(const TDictionary& d) : dictionary(d), listener(0) +TPushParser::TPushParser(ALogger& l, const TDictionary& d) : APushParser(l), dictionary(d) { init(); } -TPushParser::TPushParser(const TDictionary& d, TListener& l) : dictionary(d), listener(&l) +TPushParser::TPushParser(ALogger& l, AMathMLFactory& f, const TDictionary& d) : APushParser(l, f), dictionary(d) { init(); } +TPushParser::~TPushParser() +{ +} + void TPushParser::init() { - nextId = 1; cursor = doc.create("cursor"); - cursor["id"] = "I0"; - doc.clearDirty(); - doc.root().append(cursor); + cursor["visible"] = "1"; + hiddenCursor = 0; + reset(); } -TPushParser::~TPushParser() +void +TPushParser::reset() { + cout << "passo di qui dentro pushparser " << factory << endl; + nextId = 1; + if (cursor.parent()) cursor.remove(); + doc.reset(); + doc.root().append(cursor); + if (factory) factory->documentModified(doc); } std::string @@ -56,6 +67,65 @@ TPushParser::do_begin() } } +bool +TPushParser::correctBrace() +{ + // this method MUST be invoked when the cursor is child of a + // phantom group, which in turn is the last rightOpen MACRO's child. + // The only way to exit from a rightOpen MACRO is opening a group before + // inserting the MACRO and, once the MACRO is completely inserted, closing + // the group. + // This method return true if the condition above is true. False, otherwise. + assert(cursor.parent() && cursor.parent().isG() && !cursor.parent().hasId()); + TNode parent = cursor.parent(); + assert(parent.parent() && parent.parent().isC()); + assert(!frames.empty()); + Frame& frame = frames.top(); + assert(frame.entry.rightOpen); + assert(parent.parent().last() == parent); + + TNode c = parent.parent(); + bool stop = false; + bool ok = false; + TNode node = c.parent(); + do + { + if (node.isG() && node.hasId()) + { + // in this case, the rightOpen MACRO is a child of a group with id. + // So, the '}' is correct + ok = true; + stop = true; + } + else if (node.isG()) + { + // the MACRO is a phantom group's child. We have to control why we + // have this phantom group + TNode nodeParent = node.parent(); + if (nodeParent && nodeParent.isC()) + { + // we have to control the nature of this MACRO + const TDictionary::Entry& entry = dictionary.find(nodeParent.nameC()); + if (entry.rightOpen && node == nodeParent.last()) + { + // in this case we have to re-iterate the process + node = nodeParent.parent(); + } + else stop = true; + } + else stop = true; + } + else + { + // at the moment we assume that a MACRO cannot be child of an element other than a group + stop = true; + } + } + while (!stop); + + return ok; +} + void TPushParser::do_end() { @@ -68,25 +138,105 @@ TPushParser::do_end() } else if (parent && parent.isG() && parent.parent() && parent.parent().is("cell")) { + assert(!frames.empty()); + // closing brace for a structure in which & or \cr have been used + TNode row = parent.parent().parent(); + assert(row && row.is("row")); + assert(row.parent()); + advance(row); + } + else if (parent && parent.isG() && !parent.hasId() && parent.parent() && !parent.parent().is("math")) + { + // In this case, we have to control the cursor's grand parent. + TNode gparent = parent.parent(); + + if (gparent.isC() && gparent.last() == parent) + { + // a frame MUST be in the stack + assert(!frames.empty()); + + // we have to control the nature of this macro + if (frames.top().entry.rightOpen) + { + // in this case, the '}' character is the proper way to exit from the phantom group, and + // in particular, this character means that the user wants to exit from the MACRO. + // A rightOpen MACRO MUST be descendant of a group with Id. This '}' is the closing brace of this + // group. So, we have to control if this group exists. This groyp could exist, but this MACRO could + // be another MACRO's child, so we have to control this last MACRO recursively. This recurive control + // is done by the correctBrace method. + if (!correctBrace()) + { + // the '}' is not correct + logger.warning("nothing to close"); + } + else + { + cursor.remove(); + advance(parent); + } + + } + else + { + logger.error("closing brace ignored"); + } + } + else + { + // at the moment, a phantom group with cursor can be a MACRO's child or a cell's child, and these cases + // are handled in other blocks of code. + logger.error("do_end: strange TML tree"); + } + } + else + { + // In this case, there is a redundant '}', so we can ignore it and + // emit an error + logger.warning("There is so no corresponding'{'"); + //assert(0); + } +} + + +/* +void +TPushParser::do_end() +{ + TNode parent = cursor.parent(); + if (parent && parent.isG() && parent.hasId()) + { + // normal closing brace for an explicitly open group + cursor.remove(); + advance(parent); + } + else if (parent && parent.isG() && parent.parent() && parent.parent().is("cell")) + { + assert(!frames.empty()); // closing brace for a structure in which & or \cr have been used TNode row = parent.parent().parent(); assert(row && row.is("row")); TNode table = row.parent(); assert(table); - advance(table); + advance(row); } - else if (parent && parent.isG() && !parent.hasId() && parent.parent()) + else if (parent && parent.isG() && !parent.hasId() && parent.parent() && !parent.parent().is("math")) { // closing brace for a right-open macro (like \over) + // It's not sure that we want to exit from a phantom group with a + // '}', because, to enter in the phantom group the user didn't insert a + // '{'. cursor.remove(); - advance(parent.parent()); + advance(parent); } else { - // ??? - assert(0); + // In this case, there is a redundant '}', so I think we can ignore it and + // emit an error + logger.warning("There is so no corresponding'{'"); + //assert(0); } } +*/ void TPushParser::do_shift() @@ -115,6 +265,7 @@ TPushParser::do_shift() else { // we need two closing math shifts + //cursor.remove(); ?? parent.parent().append(cursor); } } @@ -135,7 +286,7 @@ TPushParser::do_shift() } else { - cerr << "ERROR: math shift" << endl; + logger.error("parser: math shift"); } } @@ -169,7 +320,7 @@ TPushParser::do_align() } else { - cerr << "alignment tab used outside matrix" << endl; + logger.error("alignment tab used outside matrix"); } } @@ -210,7 +361,7 @@ TPushParser::do_subscript() } else if (parent.isSb() && cursor == parent[1]) { - if (parent["under"] == "1") cerr << "already under" << endl; + if (parent["under"] == "1") logger.error("already under"); else parent["under"] = "1"; } } @@ -240,7 +391,7 @@ TPushParser::do_superscript() } else if (parent.isSp() && cursor == parent[1]) { - if (parent["over"] == "1") cerr << "already over" << endl; + if (parent["over"] == "1") logger.error("already over"); else parent["over"] = "1"; } } @@ -248,7 +399,7 @@ TPushParser::do_superscript() void TPushParser::do_space(const std::string&) { - // ? may be used to distinguish tokens in some mode? + logger.debug("do_space"); } void @@ -310,7 +461,8 @@ TPushParser::do_apostrophe() } else { - // error ??? + // is it an error? + logger.error("parser: you have to type one identifier before the ''"); } } else @@ -328,8 +480,8 @@ TPushParser::do_other(const std::string& s) do_apostrophe(); break; default: - cout << "TPushParser::do_other " << s << endl; - cout << "DOCUMENT: " << static_cast(cursor.element().get_ownerDocument()) << endl; + /*cout << "TPushParser::do_other " << s << endl; + cout << "DOCUMENT: " << static_cast(cursor.element().get_ownerDocument()) << endl;*/ TNode elem = doc.createT("o", s, nextId++); cursor.replace(elem); advance(elem); @@ -410,6 +562,7 @@ TPushParser::do_control(const std::string& name) { TNode m = doc.createC(name, nextId++); cursor.replace(m); + if (entry.leftOpen && entry.rightOpen) { assert(entry.pattern.empty()); @@ -446,6 +599,7 @@ TPushParser::do_control(const std::string& name) if (parent.isG()) { frames.push(Frame(entry)); + if (entry.paramDelimited(0)) { TNode g = doc.createG(); @@ -458,7 +612,7 @@ TPushParser::do_control(const std::string& name) else { // error, but we could handle this very easily - cerr << "error, but we could handle this easily" << endl; + logger.error(" parser:but we could handle this easily"); } } else advance(m); @@ -466,7 +620,7 @@ TPushParser::do_control(const std::string& name) break; case TDictionary::UNDEFINED: { - cerr << "ERROR: using undefined macro `" << name << "'" << endl; + logger.error("parser: using undefined macro " + name); TNode m = doc.createC(name, nextId++); cursor.replace(m); advance(m); @@ -479,130 +633,743 @@ TPushParser::do_control(const std::string& name) } void -TPushParser::gdelete_prev() +TPushParser::gdelete_prev_token() { - // if in this function, the prev of cursor does exist, also the parent and we want a graphical deleting + assert(cursor.prev()); + assert(cursor.parent()); TNode prev = cursor.prev(); - if (prev.is("i") || prev.is("o") || prev.is("n")) + assert(prev.is("i") || prev.is("o") || prev.is("n")); + + // the control below is designed to handle the case in which val have more than one unicode character + DOM::UCS4String ucs4val(prev.element().getAttribute("val")); + if ((prev.is("i")) || (ucs4val.length() <= 1) || prev.element().hasAttribute("name")) + { + cursor.remove(); + prev.replace(cursor); + + if (cursor.parent().isC()) + { + // in this case we have removed an element of a MACRO. + // we can assert that this element was a non delimited argument + assert(!frames.empty()); + Frame& frame = frames.top(); + assert(frame.pos > 0); + + frame.pos--; + } + } + else { - // the control below is designed to handle the case in which val have more than one unicode character - DOM::UCS4String ucs4val(prev.element().getAttribute("val")); - if (ucs4val.length() <= 1) + ucs4val.erase(ucs4val.length() - 1, 1); + prev.element().setAttribute(DOM::GdomeString("val"), DOM::GdomeString(ucs4val)); + } + +} + +void +TPushParser::gdelete_prev_script() +{ + // this method deletes an sp or an sb preceding the cursor + assert(cursor.prev()); + assert(cursor.parent()); + TNode prev = cursor.prev(); + assert(prev.is("sp") || prev.is("sb")); + cursor.remove(); + prev.append(cursor); + // we can invoke the gdelete_prev, because a sp (sb) MUST have two children + gdelete_prev(); +} + +void +TPushParser::gdelete_prev_group() +{ + assert(cursor.prev() && cursor.prev().isG()); + TNode prev = cursor.prev(); + cursor.remove(); + prev.append(cursor); + + // a group could have no children, so the gdelete_prev is not appropriate + // so, this method is not equivalent to the one above + do_gdelete(); +} + +void +TPushParser::gdelete_prev_macro() +{ + assert(cursor.parent()); + assert(cursor.prev()); + TNode prev = cursor.prev(); + assert(prev.isC()); + + const TDictionary::Entry& entry = dictionary.find(prev["name"]); + + if (!entry.defined()) + { + // We can assume that the user want to completely delete the undefined macro + cursor.remove(); + prev.replace(cursor); + } + else + { + // we start to remove a MACRO. Different actions must be taken, based on the nature + // of the MACRO. In some cases, we can't remove the MACRO immediately, in other + // cases it's correct. In the first set of cases, we have to update the stack, pushing + // a frame in it with a correct value of pos, in the + // second one, we must not push a frame in the stack + + if (entry.rightOpen) { + // In this fragment of code we also handle the leftOpen && rightOpen MACRO. + // if the control element is rightOpen, the cursor should be placed after + // the last child of the control element's last child, and than, we try to remove something. + // A frame MUST be pushed in the stack, because we dont' know if the following actions + // will completely remove the MACRO. + frames.push(Frame(entry)); + + // Since the MACRO is rightOpen, the last child of the MACRO must be a phantom group + assert(prev.last().isG() && !prev.last().hasId()); + cursor.remove(); - prev.replace(cursor); + prev.last().append(cursor); + + // the gdelete_prev is not appropriate, because the last child of the MACRO could have no children + do_gdelete_phantom_group(); + } + else if (entry.leftOpen) + { + // the leftOpen MACRO MUST have one and only one child, which MUST be a phantom group + // In this case, we do not have to push a frame in the stack, because we remove the + // MACRO immediately, substituting it with the content of the phantom group. + // At the moment, we don't remove the last child of the phantom group, but + // it's not clear if it's the correct behavior of the graphical deleting. + // To delete it, just remove the comment of the last instruction of the + // if (g.size()) block. + assert(prev.first()); + assert(prev.first().isG()); + assert(prev.first() == prev.last()); + + TNode g = prev.first(); + if (g.size()) + { + // in this case, the phantom group has at least one child, so we can call the + // TNode::replace. + g.remove(); + prev.replace(g.first(), TNode()); + //gdelete_prev(); + } + else + { + // otherwise, the phantom group has no children, so we remove it, also the MACRO. + cursor.remove(); + g.remove(); + prev.replace(cursor); + } } + else if (!entry.pattern.empty()) + { + // we have to start to remove a MACRO which accepts arguments. + // If the MACRO accepts arguments, the MACRO has at least one child + assert(prev.size() >= 1); + + // Differnt actions must be taken, based on the nature of the last child + // of the MACRO. We have to distinguish the case in which it's a delimited argument, + // frome the one in which it's a not delimited argument. + if (prev.last().isG() && !prev.last().hasId()) + { + // the last argument of the MACRO is a delimited argumet. We ideally remove + // the sequence of delimiters + cursor.remove(); + prev.last().append(cursor); + // we have to push a frame with a correct value of pos + assert(entry.previousParam(entry.pattern.size()) != entry.pattern.size()); + + unsigned sequence_length = entry.pattern.size() - entry.previousParam(entry.pattern.size()) - 1; + unsigned p = entry.pattern.size() - sequence_length - 1; + // now, p is the correct value of pos, and we can push the frame. + frames.push(Frame(entry, p)); + } + else + { + // in this case, the last child of the MACRO is not a delimited argument, so we try + // to remove it, but we have to take differnt actions if the MACRO is a table with rows or not. + cursor.remove(); + if (entry.table == 1 && prev.last().is("row")) + { + // in this case the cursor should be appended to the group associated to + // the last cell of the last row of the table + assert(prev.last().last().is("cell") && prev.last().last().first().isG()); + prev.last().last().first().append(cursor); + } + else + { + prev.append(cursor); + } + + // we push a frame in the stack with a correct value of member pos. + // This correct value is the size of the pattern, because we have to START to delete + // a MACRO. It means that all of the MACRO's arguments have been inserted. + frames.push(Frame(entry, entry.pattern.size())); + + if (cursor.prev()) + { + // in this case we try to remove the last child of the MACRO. + gdelete_prev(); + } + else + { + // this block of code will never be executed, because the MACRO accepts arguments, + // so a MACRO's child MUST exist. But, we can handle this case, emitting a warning + logger.warning("Parser: the TML tree is in a strange state, but a good state will be generated"); + do_gdelete(); + } + } // end of the else of the if (prev.last().isG() && !prev.last().hasId()) + + } // end of if (!entry.pattern.empty()) else { - ucs4val.erase(ucs4val.length() - 1, 1); - prev.element().setAttribute(DOM::GdomeString("val"), DOM::GdomeString(ucs4val)); + // if we are here, the MACRO preceding the cursor, is !(rightOpen || leftOpen), + // and has no pattern. It means that it has no children. + // We can replace it with the cursor + assert(prev.size() == 0); + cursor.remove(); + prev.replace(cursor); } - } // end of if (prev.is("i") || ...) - else if (prev.is("sp") || prev.is("sb")) + } // end of defined MACRO + +} + +void +TPushParser::gdelete_prev() +{ + // if in this function, the prev of cursor does exist, also the parent and we want a graphical deleting. + + assert(cursor.prev()); + assert(cursor.parent()); + + TNode prev = cursor.prev(); + + if (prev.is("i") || prev.is("o") || prev.is("n")) { - cursor.remove(); - prev.append(cursor); - do_gdelete(); - } // end of if (prev.is("sp") || prev.is("sb")) + gdelete_prev_token(); + } + else if (prev.isSp() || prev.isSb()) + { + gdelete_prev_script(); + } else if (prev.isG()) { - cursor.remove(); - prev.append(cursor); - do_gdelete(); + gdelete_prev_group(); } else if (prev.isC()) { -// cursor.remove(); -// prev.append(cursor); -// do_gdelete(); + // here, we also treat the case in which the MACRO is a table + gdelete_prev_macro(); } else { - // not handled + // not handled. Future cases... } - + } // end of method void -TPushParser::do_gdelete() +TPushParser::rgreplace_father(void) { - // this function operates a graphical deleting + // 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(); + } + + if (parent.isC()) + { + // in this case we have removed a MACRO's child. + // So, we have to update the member pos of the frame in the stack + // We can assert that this MACRO accepts arguments. + assert(!frames.empty()); + Frame& frame = frames.top(); + assert(frame.pos > 0); + frame.pos--; + } +} + +void +TPushParser::do_gdelete_script() +{ + // 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 type something like this + // $....{}^ + // or this + // $^ + // or this + // $...{^ + prev.remove(); + parent.replace(cursor); + + // if the new parent is a group with Id and the cursor is the only + // element of this group, we have to remove the group. These controls are made + // in the method rgreplace_father(). + if (cursor.parent().isG() && cursor.parent().hasId()) rgreplace_father(); + } + else + { + // in this case, the prev has to replace the script, + // and the cursor has to be placed after the prev. + assert(prev.hasId()); + parent.replace(prev); + prev.parent().append(cursor); + // now prev have a preceding element + assert(cursor.parent().size() > 1); + } +} // end of method do_gdelete_script + +void +TPushParser::do_gdelete_macro() +{ + // 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(); + + assert(!frames.empty()); + Frame& frame = frames.top(); + assert(frame.entry.pattern.size() >= 1); - // if no parent, do nothing - if (parent) + // 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. + // Being here also means that the MACRO is waiting for the first argument + // (which is not delimited), but we don't mind about it. + assert(frame.pos == 0); + parent.replace(cursor); + frames.pop(); + + // if the new parent is a group with Id, and has no elements other than the + // cursor, we can remove it, because it' a graphical deleting + if (cursor.parent() && cursor.parent().isG() && cursor.parent().hasId()) + rgreplace_father(); + else if (cursor.parent().isC()) + { + // We have assumed that a MACRO cannot be a MACRO's child. + // At the moment, this assumption is valid, but in a future + // it might be false. + assert(!frames.empty()); + Frame& frame = frames.top(); + assert(frame.pos > 0); + frame.pos--; + } + } + else { - assert(parent); - if (parent.isG()) + // a prev does exist, we have to control if it's a delimited argument or not. + if (prev.isG() && !prev.hasId()) { - TNode prev = cursor.prev(); - if (prev) + // 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()); + + // 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; + } + else + { + // the prev is not a delimited argument, so we have to try to remove it. + // We "try", because the prev might be a something that + // a simple delete cannot remove completely + gdelete_prev(); + } + } + +} + +void +TPushParser::do_gdelete_groupId() +{ + // 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 + gdelete_prev(); + + // We control if the group has to be removed, because the cursor + // might be the only element of the group. + if ((parent.first() == cursor) && parent.isG() && parent.hasId()) + rgreplace_father(); + + } + else + { + // the cursor has no preceding elements, so we have to remove the + // group. + rgreplace_father(); + + // we have to re-start the process, because it' a graphical delete + do_gdelete(); + } + +} // end of method do_gdelete_groupId() + +void +TPushParser::do_gdelete_phantom_group() +{ + // 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) + { + // the cursor has a preceding element, so we try to remove it + gdelete_prev(); + + if (parent.size() == 1 && parent.parent().isSp()) + { + // in this case the gdelete_prev has removed the only element preceding the cursor. + // If the phantom group is an sp's child, it means that the user has removed all \' in the + // phantom group. + // We can remove the phamtom group and the sp element. But we also can only remove the + // phantom group, giving the user the possibility of inserting an exponent. + // At the moment, we remove the sp element (and the phantom group), because it may be the correct + // behavior of a graphical deleting. + cursor.remove(); + parent.replace(cursor); + // now we have an sp element with two children: the first child (we don't know anything about it) + // and the cursror. + assert(cursor.parent().size() == 2); + + // to delete the script we can invoke the do_gdelete_script(), which will do all controls we need. + // To give the possibility of insetring an exponent, just remove the following istruction. + do_gdelete_script(); + } + else if (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); + } + } + 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("do_gdelete_phantom: TML tree in a inconsistent state"); + } + 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(); + + if (frame.entry.leftOpen && frame.entry.rightOpen) { - // i try to delete the preceding element - gdelete_prev(); + // 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); - // if gdelete_prev has removed all parent's child, and the group has Id, it should be removed. - // this control should be re-done on the new parent and so on. - while ((parent.first() == cursor) && parent.isG() && parent.hasId()) + 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()) { - cursor.remove(); - parent.replace(cursor); - parent = cursor.parent(); + 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(); } - else // no previous node is present + else if (frame.entry.rightOpen) { - if (!parent.parent().is("math")) + // 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(); + } + else if (frame.entry.leftOpen) + { + // this situation will never occur. + logger.error("the parser has generated a wrong TML tree"); + } + 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.replace(cursor); - - // if the new parent is a group with Id, it should be removed - if (cursor.parent().isG() && cursor.parent().hasId()) do_gdelete(); + parent.remove(); + gfather.replace(cursor); + frames.pop(); } else { - // nothing to do...i think + // 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()) + { + // 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; + cursor.remove(); + parent.remove(); + uncle.append(cursor); + } + else + { + // the uncle is a not delimited argument, so we try to remove it. + cursor.remove(); + parent.replace(cursor); + parent = cursor.parent(); // i update the parent (it should be the MACRO) + assert(parent.isC()); + + // now we try to remove the uncle (now it' the preceding element) + gdelete_prev(); + } + } // 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.warning("parser: TML tree in a strange state, but we try to recover from it"); + if (gfather.size() == 1) + { + cursor.remove(); + parent.remove(); + gfather.replace(cursor); + } + else + { + logger.warning("parser: TML tree in a very strange state, but we try to recover from it"); + cursor.remove(); + gfather.replace(cursor); } } - } - else if (parent.isC()) - { - // i think i have to implement different behaviors based on the attribute name and/or left-open - } - else if (parent.is("sp") || parent.is("sb")) + } // end of if (gfather.isC()) + else if (gfather.is("cell")) { - // being here means that a prev MUST exist + // A table is a control sequence, so there is a frame in the stack + assert(!frames.empty()); + assert(frames.top().pos == 1); + assert(frames.top().entry.table == 1); - TNode prev = cursor.prev(); + // 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()); + 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) cout << "qualcosa non va" << endl; - else + if (!prev_cell) { - if (parent.first().next() == cursor) // there's only an element preceding the cursor + // in this case, the cell is 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) { - cursor.remove(); - parent.replace(prev); - prev.parent().append(cursor); + // 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); } - else // there are two elements preceding the cursor + else { - gdelete_prev(); + // 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); } + } // 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); } + } // end of if (gfather.is("cell")) + else if (gfather.isSp()) + { + // in this case, the user typed a \'. So this phantom group + // contained a sequence of \'. + // Maybe in this part will never be used, because, if we delete last \' in the + // phantom group, we remove the phantom group also + // + // In any case, if we are here we have two possibilities: + // we can delete the phantom group; + // we can delete the superscript. + // At the moment we implement the first solution. To implement the second one, just remove + // the line code after the logger.warning and remove comments from the remaining lines + logger.warning("parser: TML tree in a strange state, we try to recover from it"); + parent.replace(cursor); + + //cursor.remove(); + //parent.remove(); + //gfather.replace(cursor); + } + else if (gfather.is("math")) + { + // in this case we ignore the user's will of deleting + // but we can decide to remove the math mode. + logger.info("Parser: nothing to delete"); } else { - // not handled: no control for tables, ... + // cursor's grand father is undefined + logger.error("parser: TML tree is in a unknown state"); } + } // end of the else of the if (prev) + +} + + +void +TPushParser::do_gdelete() +{ + // 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"); } else { - // do nothing ?? - } - -} // end of method + // 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"); + } + 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()) + { + do_gdelete_groupId(); + } + else + { + do_gdelete_phantom_group(); + } + } // end of parent is group + else if (parent.isC()) + { + do_gdelete_macro(); + } // end of parent is a MACRO + else if (parent.isSp() || parent.isSb()) + { + do_gdelete_script(); + } // end of parent is sp or sb + } // end of the else which consider the case in which parent exists + +} // end of method do_gdelete void TPushParser::process(const TToken& token) @@ -624,17 +1391,16 @@ TPushParser::process(const TToken& token) case TToken::ACTIVE: do_active(token.value); break; case TToken::COMMENT: do_comment(); break; case TToken::CONTROL: do_control(token.value); break; - case TToken::GDELETE: do_gdelete(); break; } } void TPushParser::push(const TToken& token) { - cerr << "TPushParser::push " << token.value << " (cat: " << token.category << ")" << endl; TNode parent = cursor.parent(); // If the cursor has no parent then it is detached from the editing // tree, which means this token will be ignored + if (parent) // If the parent is a phantom group and the grand-parent is a // control sequence, there are two cases: @@ -654,38 +1420,54 @@ TPushParser::push(const TToken& token) assert(frame.entry.pattern[frame.pos + 1].category != TToken::PARAMETER); if (frame.entry.pattern[frame.pos + 1] == token) { - // The token matches with the delimiter of the argument. - cursor.remove(); - - // If the phantom group has just one child, it is not necessary - // and can be removed. However, this would complicate - // all the code that follows, so given it is a rare case we - // leave it alone - // if (parent.size() == 1) parent.replace(parent[0]); - - // Eat both the argument and its delimiter - frame.pos += 2; - if (frame.pos == frame.entry.pattern.size()) - { - // This token has completed the entry - advance(parent.parent()); - } - else if (frame.entry.paramDelimited(frame.pos)) + // The token matches with a delimiter of the argument, + // hence we increment the frame.pos + frame.pos++; + + if (frame.entry.lastDelimiter(frame.pos)) { - // For the next is a delimited argument we have to place - // a suitable phantom group with the cursor inside - TNode g = doc.createG(); - parent.parent().append(g); - g.append(cursor); + // this delimiter is the last one for the argumet, + // so the argument is completed + cursor.remove(); + frame.pos++; + + if (frame.pos == frame.entry.pattern.size()) + { + // This token has completed the entry + advance(parent); + } + else if (frame.entry.paramDelimited(frame.pos)) + { + // For the next is a delimited argument we have to place + // a suitable phantom group with the cursor inside + TNode g = doc.createG(); + parent.parent().append(g); + g.append(cursor); + } + else + parent.parent().append(cursor); } - else - parent.parent().append(cursor); } else { - // Delimiter mismatch. Since we are parsing a delimited - // argument we just process the token - process(token); + // Delimiter mismatch. + if (frame.entry.pattern[frame.pos].category != TToken::PARAMETER) + { + // in this case, there is a sequence of delimiters that delimitates + // the argument, and the user has correctly inserted a portion of this + // sequence, but now has inserted a wrong delimiter. + // Here, there are some possibilities: + // - ignore the token, and wait for the correct delimiter + // - ignore the token, wait for the correct delimiter and emit an error + // At the moment, we implement the second possibily + logger.error("parser: it's not the correct delimiter...you have to type " + frame.entry.pattern[frame.pos + 1].value); + } + else + { + // in this case, the sequence of delimiters is composed of one + // delimiter. It means that we have to process the token + process(token); + } } } else @@ -704,6 +1486,7 @@ TPushParser::push(const TToken& token) // or a fixed token Frame& frame = frames.top(); assert(frame.pos < frame.entry.pattern.size()); + if (frame.entry.pattern[frame.pos].category == TToken::PARAMETER) { // As by the TeX parsing rules of undelimited parameters, @@ -738,7 +1521,7 @@ TPushParser::push(const TToken& token) else { // There is a mismatch. Emit an error and ignore the token? - cerr << "ERROR: token ignored: " << token.value << " (cat: " << token.category << ")" << endl; + logger.debug("parser: token ignored: " + token.value); } } @@ -746,12 +1529,127 @@ TPushParser::push(const TToken& token) process(token); else { - cout << "ignored token" << endl; + logger.warning("ignored token"); + } + + if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc); +} + +std::string +TPushParser::drop() +{ + do_gdelete(); + if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc); + return ""; +} + + +void +TPushParser::advance(const TNode& node) +{ + assert(node); + + if (!node.parent()) + { + // this is an error + logger.error("wrong TML tree"); + } + else if (node.parent().isG()) + { + TNode next = node.next(); + if (next) next.insert(cursor); + else node.parent().append(cursor); + } + else if (node.parent().isC()) + { + assert(!frames.empty()); + if (frames.top().pos == frames.top().entry.pattern.size()) + { + if (frames.top().entry.rightOpen) + { + // we have to remove the frame from the stack + frames.pop(); + advance(node.parent().parent()); + } + else + { + frames.pop(); + advance(node.parent()); + } + } + else if (frames.top().entry.paramDelimited(frames.top().pos)) + { + // the next argument is delimited, so we have to create a phantom group + TNode g = doc.createG(); + g.append(cursor); + node.parent().append(g); + } + else + { + // the next argumet is not delimited, so we have to append the cursor + // to the MACRO + node.parent().append(cursor); + } } + else advance(node.parent()); +} - if (listener) listener->callback(doc); + +/* + * This version handles the case in which we have to + * create a delimited argument + +void +TPushParser::advance(const TNode& node) +{ + assert(node); + TNode parent = node.parent(); + if (!parent) + ; // nothing to do, the cursor is not in the document any more + else if (parent.isG()) + { + TNode next = node.next(); + if (next) next.insert(cursor); + else parent.append(cursor); + } + else if (parent.isC()) + { + if (node.next()) + ; // cursor removed + else + { + Frame& frame = frames.top(); + if (frame.pos == frame.entry.pattern.size()) + { + frames.pop(); + advance(parent); + } + else if (frame.entry.paramDelimited(frame.pos)) + { + // the next argument is delimited, so we have to create a phantom group + // with the cursor inside. We have to remember that, since we are here, + // the cursor has been removed + TNode g = doc.createG(); + g.append(cursor); + parent.append(g); + } + else + { + // otherwise, the next MACRO's argument is not delimited, so we just + // append the cursor to the MACRO + parent.append(cursor); + } + } + } + else if (parent.is("math")) + ; // we are done + else + advance(parent); } +*/ +/* + * original advance void TPushParser::advance(const TNode& node) { @@ -786,10 +1684,53 @@ TPushParser::advance(const TNode& node) else advance(parent); } +*/ + void -TPushParser::setCursor(const std::string& c) +TPushParser::setCursorHint(const std::string& c) +{ + if (cursor["val"] != c) + { + cursor["val"] = c; + if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc); + } +} + +bool +TPushParser::hideCursor() { - cursor["val"] = c; - if (listener) listener->callback(doc); + if (hiddenCursor++ == 0) + { + cursor["visible"] = "0"; + if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc); + return true; + } + else + return false; +} + +bool +TPushParser::showCursor() +{ + if (hiddenCursor > 0 && --hiddenCursor == 0) + { + cursor["visible"] = "1"; + if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc); + return true; + } + else + return false; +} + +bool +TPushParser::thaw() +{ + if (APushParser::thaw() && factory && doc.dirtyNode()) + { + factory->documentModified(doc); + return true; + } + else + return false; }