X-Git-Url: http://matita.cs.unibo.it/gitweb/?p=helm.git;a=blobdiff_plain;f=helm%2FDEVEL%2Fmathml_editor%2Fsrc%2FTPushParser.cc;fp=helm%2FDEVEL%2Fmathml_editor%2Fsrc%2FTPushParser.cc;h=0000000000000000000000000000000000000000;hp=0c96fe43968c94be52a1fdcffac385012d683ebb;hb=3ef089a4c58fbe429dd539af6215991ecbe11ee2;hpb=1c7fb836e2af4f2f3d18afd0396701f2094265ff diff --git a/helm/DEVEL/mathml_editor/src/TPushParser.cc b/helm/DEVEL/mathml_editor/src/TPushParser.cc deleted file mode 100644 index 0c96fe439..000000000 --- a/helm/DEVEL/mathml_editor/src/TPushParser.cc +++ /dev/null @@ -1,1952 +0,0 @@ -/* This file is part of EdiTeX, an editor of mathematical - * expressions based on TeX syntax. - * - * Copyright (C) 2002-2003 Luca Padovani , - * 2003 Paolo Marinelli . - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * For more information, please visit the project's home page - * http://helm.cs.unibo.it/editex/ - * or send an email to - */ - -#include - -#include "ALogger.hh" -#include "TPushParser.hh" -#include "AMathMLFactory.hh" - -TPushParser::TPushParser(ALogger& l, const TDictionary& d) : APushParser(l), dictionary(d) -{ - init(); -} - -TPushParser::TPushParser(ALogger& l, AMathMLFactory& f, const TDictionary& d) : APushParser(l, f), dictionary(d) -{ - init(); -} - -TPushParser::~TPushParser() -{ -} - -void -TPushParser::init() -{ - cursor = doc.create("cursor"); - cursor["visible"] = "1"; - hiddenCursor = 0; - reset(); -} - -void -TPushParser::reset() -{ - nextId = 1; - if (cursor.parent()) cursor.remove(); - cursor["val"] = ""; - doc.reset(); - doc.root().append(cursor); - if (factory && !frozen()) factory->documentModified(doc); -} - -TNode -TPushParser::PRIME() -{ - const TDictionary::Entry entry = dictionary.find("prime"); - if (entry.cls == TDictionary::OPERATOR) - { - TNode op = doc.createO(entry.value, nextId++); - op["name"] = "prime"; - return op; - } - else - { - TNode op = doc.createO("?", nextId++); - return op; - } -} - -bool -TPushParser::do_begin() -{ - TNode parent = cursor.parent(); - if (parent.isC() && dictionary.find(parent.nameC()).table) - { - TNode row = doc.create("row"); - TNode cell = doc.create("cell"); - TNode g = doc.createG(); - row.append(cell); - cell.append(g); - g.append(cursor); - parent.append(row); - } - else - { - TNode g = doc.createG(nextId++); - cursor.replace(g); - g.append(cursor); - } - return true; -} - -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; -} - -bool -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); - return true; - } - 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); - return true; - } - 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"); - return false; - } - else - { - cursor.remove(); - advance(parent); - return true; - } - } - else - { - logger.warning("ignored closing brace"); - return false; - } - } - else - { - // at the moment, a phantom group with the cursor inside 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"); - return false; - } - } - else - { - // In this case, there is a redundant '}', so we can ignore it and - // emit an error - logger.warning("There is so no corresponding'{'"); - return false; - //assert(0); - } -} - -bool -TPushParser::do_shift() -{ - TNode parent = cursor.parent(); - assert(parent); - if (parent.is("tex")) - { - TNode math = doc.create("math", nextId++); - TNode g = doc.createG(); - cursor.replace(math); - math.append(g); - g.append(cursor); - return true; - } - else if (parent.isG() && !parent.hasId() && parent.parent() && parent.parent().is("math")) - { - if (cursor.prev()) - { - // there is something before the cursor, hence this is the - // closing math shift - if (parent.parent()["display"] != "1") - { - // one math shift is enough to close it - cursor.remove(); - return true; - } - else - { - // we need two closing math shifts - //cursor.remove(); ?? - parent.parent().append(cursor); - return true; // ??? - } - } - else if (parent.parent()["display"] != "1") - { - // there is nothing before the cursor, and the math is not - // in display mode, so this must be a double math shift - parent.parent()["display"] = "1"; - return true; - } - else - { - parent.parent().append(cursor); - return true; - } - } - else if (parent.is("math")) - { - cursor.remove(); - return true; - } - else - { - logger.warning("not allowed here"); - return false; - } -} - -bool -TPushParser::do_align() -{ - TNode parent = cursor.parent(); - if (parent && parent.isG() && parent.hasId()) - { - // alignment tab used for the first time inside a group - TNode row = doc.create("row"); - TNode cell = doc.create("cell"); - TNode g = doc.createG(); - row.append(cell); - cell.append(g); - g.append(parent.first(), cursor); - return true; - } - else if (parent && parent.isG() && parent.parent().is("cell")) - { - // alignment tab used within a cell - TNode oldCell = parent.parent(); - assert(oldCell && oldCell.is("cell")); - TNode row = oldCell.parent(); - assert(row && row.is("row")); - TNode cell = doc.create("cell"); - if (oldCell.next()) oldCell.next().insert(cell); - else row.append(cell); - TNode g = doc.createG(); - cell.append(g); - g.append(cursor); - return true; - } - else - { - logger.warning("alignment tab used outside matrix"); - return false; - } -} - -bool -TPushParser::do_eol() -{ - //if (cursor.parent()) cursor.remove(); - logger.warning("ignored token"); - return false; -} - -bool -TPushParser::do_parameter(const std::string& p) -{ - logger.warning("ignored token"); - return false; -} - -bool -TPushParser::do_subscript() -{ - TNode parent = cursor.parent(); - if (parent.isG()) - { - TNode prev = cursor.prev(); - if (!prev) - { - TNode elem = doc.create("sb", nextId++); - TNode g = doc.createG(); - cursor.replace(elem); - elem.append(g); - elem.append(cursor); - return true; - } - else - { - TNode elem = doc.create("sb", nextId++); - prev.replace(elem); - elem.append(prev); - elem.append(cursor); - return true; - } - } - else if (parent.isSb() && cursor == parent[1]) - { - if (parent["under"] == "1") - { - logger.warning("already under"); - return false; - } - else - { - parent["under"] = "1"; - return true; - } - } - else - { - logger.warning("ignored token"); - return false; - } -} - -bool -TPushParser::do_superscript() -{ - TNode parent = cursor.parent(); - if (parent.isG()) - { - TNode prev = cursor.prev(); - if (!prev) - { - TNode elem = doc.create("sp", nextId++); - TNode g = doc.createG(); - cursor.replace(elem); - elem.append(g); - elem.append(cursor); - return true; - } - else - { - TNode elem = doc.create("sp", nextId++); - prev.replace(elem); - elem.append(prev); - elem.append(cursor); - return true; - } - } - else if (parent.isSp() && cursor == parent[1]) - { - if (parent["over"] == "1") - { - logger.warning("already over"); - return false; - } - else - { - parent["over"] = "1"; - return true; - } - } - else - { - logger.warning("ignored token"); - return false; - } -} - -bool -TPushParser::do_ignorablespace(const std::string& s) -{ - // At the moment, do nothing -} - -bool -TPushParser::do_space(const std::string&) -{ - TNode elem = doc.createS(nextId++); - cursor.replace(elem); - advance(elem); - return true; -} - -bool -TPushParser::do_letter(const std::string& s) -{ - //TNode parent = cursor.parent(); - TNode elem = doc.createI(s, nextId++); - cursor.replace(elem); - advance(elem); - return true; -} - -bool -TPushParser::do_digit(const std::string& s) -{ - TNode elem = doc.createN(s, nextId++); - cursor.replace(elem); - advance(elem); - return true; -} - -bool -TPushParser::isPrimes(const TNode& node) const -{ - assert(node); - return node.isG() && node.last() && node.last().is("o") && node.last()["name"] == "prime"; -} - -bool -TPushParser::do_apostrophe() -{ - if (cursor.parent() && cursor.parent().isG()) - { - if (TNode prev = cursor.prev()) - { - if (prev.isSp() && prev[1] && isPrimes(prev[1])) - { - prev[1].append(PRIME()); - return true; - } - else if (prev.isSb() && prev[0] && - prev[0].isSp() && prev[0][1] && - isPrimes(prev[0][1])) - { - prev[0][1].append(PRIME()); - return true; - } - else - { - TNode elem = doc.create("sp"); - TNode g = doc.createG(); - prev.replace(elem); - elem.append(prev); - elem.append(g); - g.append(PRIME()); - return true; - } - } - else - { - // is it an error? - logger.warning("you have to insert an identifier before a ''"); - return false; - } - } - else - { - logger.warning("cursor has to be in a group"); - return false; - } -} - -bool -TPushParser::do_other(const std::string& s) -{ - switch (s[0]) - { - case '\'': - return do_apostrophe(); - break; - default: - /*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); - return true; - break; - } -} - -bool -TPushParser::do_active(const std::string&) -{ - // ??? space? - logger.warning("ignored token"); - return false; -} - -bool -TPushParser::do_comment() -{ - // ??? - return false; -} - -bool -TPushParser::do_cr() -{ - TNode parent = cursor.parent(); - if (parent && parent.isG() && - parent.parent() && parent.parent().is("cell") && - parent.parent().parent() && parent.parent().parent().is("row")) - { - TNode oldRow = parent.parent().parent(); - assert(oldRow); - TNode table = oldRow.parent(); - assert(table); - TNode row = doc.create("row"); - TNode cell = doc.create("cell"); - TNode g = doc.createG(); - if (oldRow.next()) oldRow.next().insert(row); - else table.append(row); - row.append(cell); - cell.append(g); - g.append(cursor); - return true; - } - else - { - // at the moment, \cr can only be used inside a table - logger.warning("cr used outside a table"); - return false; - } -} - -bool -TPushParser::do_control(const std::string& name) -{ - if (name == "cr") return do_cr(); - else - { - TNode parent = cursor.parent(); - const TDictionary::Entry& entry = dictionary.find(name); - switch (entry.cls) - { - case TDictionary::IDENTIFIER: - { - TNode t = doc.createI(entry.value, nextId++); - t["name"] = name; - cursor.replace(t); - advance(t); - return true; - } - break; - case TDictionary::OPERATOR: - { - TNode t = doc.createO(entry.value, nextId++); - t["name"] = name; - cursor.replace(t); - advance(t); - return true; - } - break; - case TDictionary::NUMBER: - { - TNode t = doc.createN(entry.value, nextId++); - t["name"] = name; - cursor.replace(t); - advance(t); - return true; - } - break; - case TDictionary::MACRO: - { - if (parent.isG()) - { - TNode m = doc.createC(name, nextId++); - cursor.replace(m); - if (entry.leftOpen && entry.rightOpen) - { - assert(entry.pattern.empty()); - assert(parent.isG()); - TNode g1 = doc.createG(); - g1["left-open"] = "1"; - g1.append(parent.first(), m); - m.append(g1); - TNode g2 = doc.createG(); - g2.append(cursor); - m.append(g2); - frames.push(Frame(entry)); - } - else if (entry.leftOpen) - { - assert(parent.isG()); - TNode g = doc.createG(); - g["left-open"] = "1"; - g.append(parent.first(), m); - m.append(g); - advance(m); - } - else if (entry.rightOpen) - { - assert(entry.pattern.empty()); - assert(parent.isG()); - TNode g = doc.createG(); - g.append(cursor); - m.append(g); - frames.push(Frame(entry)); - } - else if (!entry.pattern.empty()) - { - frames.push(Frame(entry)); - if (entry.paramDelimited(0)) - { - TNode g = doc.createG(); - m.append(g); - g.append(cursor); - } - else - m.append(cursor); - } - else - { - // it's an empty macro - advance(m); - } - return true; - } - else if (!entry.pattern.size() && !entry.rightOpen && !entry.leftOpen) - { - // a macro with no arguments and no right open and no left open, can be child of anything - TNode m = doc.createC(name, nextId++); - cursor.replace(m); - advance(m); - return true; - } - else - { - // a macro with arguments or a rightOpen or leftOpen macro must be a group's child - logger.warning("ignored token: this macro should be in a group"); - return false; - } - } - break; - case TDictionary::UNDEFINED: - { - logger.warning("using undefined macro " + name); - TNode m = doc.createC(name, nextId++); - cursor.replace(m); - advance(m); - return true; - } - break; - default: - { - //assert(0); - logger.warning("ignored token"); - return false; - } - } - } -} - -std::string -TPushParser::drop_prev_token(bool special) -{ - assert(cursor.prev()); - assert(cursor.parent()); - TNode prev = cursor.prev(); - assert(prev.isT()); - - DOM::UCS4String ucs4val = prev.element().getAttribute("val"); - bool macro = prev.element().hasAttribute("name"); - std::string utf8name; - if (macro) utf8name = prev.element().getAttribute("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--; - } - - if ((ucs4val.length() > 1)) - { - if (!macro) - { - // in this case we can return the content of ucs4val, but we have - // to convert it in a utf8 - DOM::GdomeString gdsval(ucs4val); - std::string utf8val(gdsval); - switch (utf8val[utf8val.length() - 1]) - { - case '-': - case '_': - return (special) ? std::string(utf8val, 0, utf8val.length() - 1) + "\\" : std::string(utf8val, 0, utf8val.length() - 1); - default: return (special) ? "" : std::string(utf8val, 0, utf8val.length() - 1); - } - } - else - { - // in this case, the content of val could be in unicode, - // but we have the attribute name, which doesn't contain character not representable - // with a byte. - return (special) ? "\\" + utf8name : ""; - } - } - else if (macro && special) return "\\" + utf8name; - else return ""; -} - -std::string -TPushParser::drop_prev_script(bool special) -{ - // 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 drop_prev, because a sp (sb) MUST have two children - // but we cannot invoke do_drop_script because it assumes when called, the first - // child has been removed yet. - if (cursor.prev().isG() && !prev.hasId()) - { - // in this case, the user has inserted a sequence of '. - // Hence, we force a normal deletion, because the behavior must be the same - // for the two kind of deletion - return drop_prev(false); - } - else return drop_prev(special); -} - -std::string -TPushParser::drop_prev_group(bool special) -{ - assert(cursor.prev() && cursor.prev().isG()); - TNode parent = cursor.parent(); - TNode prev = cursor.prev(); - cursor.remove(); - prev.append(cursor); - - if (parent.isC() && prev.hasId()) - { - // this previous group is a macro's argument. Entering inside it means that - // this argument becomes incomplete. Hence, we have to decrement the member pos. - assert(!frames.empty()); - frames.top().pos--; - } - - if (special) return ""; - else - { - // a group could have no children, so the drop_prev is not appropriate - // so, this method is not equivalent to the one above - return do_drop(special); - } -} - -std::string -TPushParser::drop_prev_macro(bool special) -{ - assert(cursor.parent()); - assert(cursor.prev()); - TNode prev = cursor.prev(); - assert(prev.isC()); - - std::string macro_name = prev.nameC(); - - TNode parent = cursor.parent(); - - const TDictionary::Entry& entry = dictionary.find(prev["name"]); - - if (!entry.defined()) - { - // In this case, with a normal deletion, we completely remove the macro. - // With a special deletion, we remove the last character of the macro's name. - cursor.remove(); - prev.replace(cursor); - if (cursor.parent().isC()) - { - // we have removed a macro's child - assert(!frames.empty()); - frames.top().pos--; - } - if (special) return "\\" + macro_name.erase(macro_name.length() - 1, 1); // we remove the last char, because an undefined macro's name is visible - return ""; - } - 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. - // since 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.last().append(cursor); - - if (special) return ""; - else - { - // the drop_prev is not appropriate, because the last child of the MACRO could have no children - return do_drop_phantom_group(special); - } - } - 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. - // We could remove the last child of the phantom group, but - // it's not clear if it's the correct behavior of the graphical deletion. - // At the moment, to give a standard behavior, we remove the last element. - // With a special deletion, we do not remove it. - 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()); - parent.append(cursor); - if (special) return "\\" + macro_name; - else return do_drop(special); - } - else - { - // otherwise, the phantom group has no children, so we remove it, also the MACRO. - cursor.remove(); - g.remove(); - prev.replace(cursor); - if (special) return "\\" + macro_name; - else - { - // Once removed this empty macro, we could try to remove something else. - // This would be justified by the fact that, generally, an empty macro gives no visual information - // about it. - return do_drop(special); // special is false - } - } - } - 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()) - { - if (special) - { - // in this case, we have to start removing the last delimiter - frames.push(Frame(entry, entry.pattern.size() - 2)); - - cursor.remove(); - prev.last().append(cursor); - - std::string last_del = entry.pattern[entry.pattern.size() - 1].value; - - return "\\" + last_del; - } - else - { - // 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)); - - // To give a standard behavior to the graphical deletion, we remove the last - // element of the macro. Since we are in a phantom group, we can invoke the - // do_drop_phantom_group(special). - return do_drop_phantom_group(special); - } - } - 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 has to 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); - - // we have to push a frame in the stack. Since tables has a pattern size = 1, we have to - // set pos at 0, because appending the cursor to the last cell means that this argument - // is not whole inserted. - // We don't call frames.push(Frame(entry)), because it incoditionaly set pos at 0. The - // following line is more general - frames.push(Frame(entry, entry.pattern.size() - 1)); - if (special) - { - // to type a table with rows and cells, the user had typed a - // "{", and to exit from it, the user had inserted a "}". - // Since we are in a special deletion, we just idealy remove the "}" - return ""; - } - else return do_drop_phantom_group(special); - } - else - { - // we push a frame in the stack with a correct value of member pos. - // This correct value is the size of the pattern - 1, because we have been started to delete - // a MACRO. It means that all of the MACRO's arguments have been inserted, but - frames.push(Frame(entry, entry.pattern.size())); - prev.append(cursor); - return drop_prev(special); - } - - } // end of the else of the if (prev.last().isG() && !prev.last().hasId()) - - } // end of if (!entry.pattern.empty()) - else - { - // 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); - if (cursor.parent().isC()) - { - // we have removed an empty macro, which was a non delimited argument of a macro. - // We have to decrement pos - assert(!frames.empty()); - frames.top().pos--; - } - - if (special) return "\\" + macro_name; - else return ""; - - // now we could start to remove something else. This behavior would be justified by the - // fact that, generally, an empty MACRO gives no visual information about it. - // To adopt this behavior, just remove the comment to the following instruction - // return do_drop(special); - } - } // end of defined MACRO - -} - -std::string -TPushParser::drop_prev(bool special) -{ - // if in this function, the prev of cursor does exist, also the parent and we want a graphical deletion. - - assert(cursor.prev()); - assert(cursor.parent()); - - TNode prev = cursor.prev(); - - if (prev.isT()) - { - return drop_prev_token(special); - } - else if (prev.isSp() || prev.isSb()) - { - return drop_prev_script(special); - } - else if (prev.isG()) - { - return drop_prev_group(special); - } - else if (prev.isC()) - { - // here, we also treat the case in which the MACRO is a table - return drop_prev_macro(special); - } - else - { - // not handled. Future cases... - return ""; - } - -} // end of method - -void -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 -TPushParser::process(const TToken& token) -{ - switch (token.category) - { - case TToken::BEGIN: return do_begin(); - case TToken::END: return do_end(); - case TToken::SHIFT: return do_shift(); - case TToken::ALIGN: return do_align(); - case TToken::EOL: return do_eol(); - case TToken::PARAMETER: return do_parameter(token.value); - case TToken::SUPERSCRIPT: return do_superscript(); - case TToken::SUBSCRIPT: return do_subscript(); - case TToken::IGNORABLE_SPACE: return do_ignorablespace(token.value); - case TToken::SPACE: return do_space(token.value); - case TToken::LETTER: return do_letter(token.value); - case TToken::DIGIT: return do_digit(token.value); - case TToken::OTHER: return do_other(token.value); - case TToken::ACTIVE: return do_active(token.value); - case TToken::COMMENT: return do_comment(); - case TToken::CONTROL: return do_control(token.value); - } -} - -void -TPushParser::push(const TToken& token) -{ - 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: - // a. we are parsing a delimited argument of a entry - // b. we are parsing a side of a right- or left-open entry - if (parent.isG() && !parent.hasId() && parent.parent().isC()) - { - // There must be an open frame, for the grand-parent is a control sequence - assert(!frames.empty()); - Frame& frame = frames.top(); - if (!frame.entry.pattern.empty()) - { - // The entry pattern is not empty. By our conventions this means - // the entry cannot be open at either end, hence we are parsing - // a delimited argument - assert(frame.pos + 1 < frame.entry.pattern.size()); - assert(frame.entry.pattern[frame.pos + 1].category != TToken::PARAMETER); - if (frame.entry.pattern[frame.pos + 1] == token) - { - // The token matches with a delimiter of the argument, - // hence we increment the frame.pos - frame.pos++; - - if (frame.entry.lastDelimiter(frame.pos)) - { - // this delimiter is the last one for the argumet, - // so the argument is completed - cursor.remove(); - advance(parent); - } - } - else - { - // 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.warning("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 - { - // The entry pattern is empty, hence we are parsing a right-open - // entry. What happens if we actually are in the left side? - // This could happen only when re-editing an entered expression - // We'll see... - assert(frame.entry.rightOpen); - process(token); - } - } - else if (parent.isC()) - { - // We are parsing a non-delimited argument entry - // 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, - // empty spaces are ignored - if ((token.category != TToken::SPACE) && (token.category != TToken::IGNORABLE_SPACE)) process(token); - } - else if (frame.entry.pattern[frame.pos] == token) - { - // The token has been accepted - frame.pos++; - if (frame.pos < frame.entry.pattern.size() && - frame.entry.paramDelimited(frame.pos)) - { - // If the next is a delimited argument we have to place - // the phantom group with the cursor inside - TNode g = doc.createG(); - cursor.replace(g); - g.append(cursor); - } - else - { - cursor.remove(); - advance(parent); - } - } - else - { - // There is a mismatch. Emit an error and ignore the token? - logger.warning("ignored token: " + token.value); - } - } - else - process(token); - else - { - logger.warning("ignored token"); - } - - if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc); -} - -std::string -TPushParser::drop(bool special) -{ - std::string str = do_drop(special); - if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc); - return str; -} - -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 + 1 == frames.top().entry.pattern.size()) || (frames.top().entry.pattern.empty())) - { - // we are here when we have a right open macro, or the inserted element is the last one - 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 + 1)) - { - // the next argument is delimited, so we have to create a phantom group - TNode g = doc.createG(); - g.append(cursor); - node.parent().append(g); - frames.top().pos++; - } - else - { - // the next argumet is not delimited, so we have to append the cursor - // to the MACRO - node.parent().append(cursor); - frames.top().pos++; - } - } - else advance(node.parent()); -} - -void -TPushParser::setCursorHint(const std::string& c) -{ - if (cursor["val"] != c) - { - cursor["val"] = c; - if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc); - } -} - -bool -TPushParser::hideCursor() -{ - 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; -}