From bf29ef82dc843dcb1929b413b36801ac26441fbd Mon Sep 17 00:00:00 2001 From: Paolo Marinelli Date: Thu, 30 Jan 2003 21:15:41 +0000 Subject: [PATCH] Now it's possible to insert and delete control sequence with arguments delimited by a sequence of delimiters, and to insert and delete tables. To meet this goals, some source files have been modified. src/TPushLexer.* This class has a new method: flush. It's can be used to tell the lexer to send the current content of the buffer to the parser. It's usefull in the creation of the dictionary. src/TDictionary.* There are two new method: bool lastDelimiter(unsigned p) and unsigned previousParam(unsigne p). The first one is used to know if the delimiter at position p is the last one of a sequence. The second one retursn the position of the parameter preceding p. src/TPushParser.cc Now, it provides support for inserting and deleting macro with arguments delimetd by a sequence of delimiters, and for inserting and deleting tables. dictionary.xml there are three new entry, added only for testing purposes. xsl/tml-mmlp.xsl it handles the three new element, introduced in the dictionary. --- helm/DEVEL/mathml_editor/dictionary.xml | 4 +- helm/DEVEL/mathml_editor/src/TDictionary.cc | 31 +- helm/DEVEL/mathml_editor/src/TDictionary.hh | 4 +- helm/DEVEL/mathml_editor/src/TPushLexer.cc | 23 +- helm/DEVEL/mathml_editor/src/TPushLexer.hh | 1 + helm/DEVEL/mathml_editor/src/TPushParser.cc | 373 +++++++++++++------- helm/DEVEL/mathml_editor/src/TTokenizer.cc | 3 +- helm/DEVEL/mathml_editor/test/editor.cc | 1 + helm/DEVEL/mathml_editor/xsl/tml-mmlp.xsl | 37 ++ 9 files changed, 346 insertions(+), 131 deletions(-) diff --git a/helm/DEVEL/mathml_editor/dictionary.xml b/helm/DEVEL/mathml_editor/dictionary.xml index 023bcdbe1..4c7045d48 100644 --- a/helm/DEVEL/mathml_editor/dictionary.xml +++ b/helm/DEVEL/mathml_editor/dictionary.xml @@ -348,6 +348,8 @@ - + + + diff --git a/helm/DEVEL/mathml_editor/src/TDictionary.cc b/helm/DEVEL/mathml_editor/src/TDictionary.cc index 303d743d4..dcaf1d627 100644 --- a/helm/DEVEL/mathml_editor/src/TDictionary.cc +++ b/helm/DEVEL/mathml_editor/src/TDictionary.cc @@ -134,7 +134,7 @@ TDictionary::load(const char* uri) { if (entry.cls != MACRO) cerr << "WARNING: `" << name << "' table ignored for non-macro" << endl; - + std::istringstream is(el.getAttribute("table")); unsigned table; is >> table; @@ -166,3 +166,32 @@ TDictionary::Entry::paramDelimited(unsigned i) const // AND the next argument is not a parameter return i + 1 < pattern.size() && pattern[i + 1].category != TToken::PARAMETER; } + +bool +TDictionary::Entry::lastDelimiter(unsigned i) const +{ + assert(i < pattern.size()); + assert(pattern[i].category != TToken::PARAMETER); + // a token is the last delimiter if it is the last token + // of the pattern or if the next token is a parameter) + return i + 1 == pattern.size() || pattern[i + 1].category == TToken::PARAMETER; +} + +unsigned +TDictionary::Entry::previousParam(unsigned i) const +{ + // this method return the position in the pattern of the + // parameter placed in a position preceding i. + // If no preceding i parameter present, the method return + // pattern.size(). + // To know the position of the last parameter, call this + // method with i == pattern.size() + unsigned j = i - 1; + + while (pattern[j].category != TToken::PARAMETER) + { + if (j) j--; + else return pattern.size(); + } + return j; +} diff --git a/helm/DEVEL/mathml_editor/src/TDictionary.hh b/helm/DEVEL/mathml_editor/src/TDictionary.hh index 44c020cbe..b769aaa4e 100644 --- a/helm/DEVEL/mathml_editor/src/TDictionary.hh +++ b/helm/DEVEL/mathml_editor/src/TDictionary.hh @@ -36,7 +36,7 @@ public: { cls = UNDEFINED; infix = prefix = postfix = 0; - delimiter = limits = embellishment = leftOpen = rightOpen = 0; + table = delimiter = limits = embellishment = leftOpen = rightOpen = 0; }; std::vector pattern; @@ -45,6 +45,8 @@ public: bool defined(void) const { return cls != UNDEFINED; }; bool hasArguments(void) const { return !pattern.empty(); }; bool paramDelimited(unsigned) const; + bool lastDelimiter(unsigned) const; + unsigned previousParam(unsigned) const; EntryClass cls; unsigned infix : 8; diff --git a/helm/DEVEL/mathml_editor/src/TPushLexer.cc b/helm/DEVEL/mathml_editor/src/TPushLexer.cc index 4b24523da..466ee2bd8 100644 --- a/helm/DEVEL/mathml_editor/src/TPushLexer.cc +++ b/helm/DEVEL/mathml_editor/src/TPushLexer.cc @@ -15,6 +15,12 @@ TPushLexer::reset() state = ACCEPT; } +void +TPushLexer::flush() +{ + push(-1); +} + void TPushLexer::transaction(char ch, State newState) { @@ -50,6 +56,7 @@ TPushLexer::push(char ch) if (ch == '\\') state = ESCAPE; else if (ch == '#') state = PARAMETER; else if (ch == '\b') parser.push(TToken(TToken::GDELETE)); + else if (ch == -1) ; else transaction(ch, ACCEPT); break; case ESCAPE: @@ -62,6 +69,7 @@ TPushLexer::push(char ch) { state = ACCEPT; } + else if (ch == -1) error(); else { parser.push(TToken(TToken::CONTROL, ch)); @@ -88,6 +96,12 @@ TPushLexer::push(char ch) } else if (isalpha(ch)) buffer.push_back(ch); + else if (ch == -1) + { + parser.push(TToken(TToken::CONTROL, buffer)); + buffer.erase(); + state = ACCEPT; + } else { parser.push(TToken(TToken::CONTROL, buffer)); @@ -101,11 +115,16 @@ TPushLexer::push(char ch) else if (ch == '#') state = PARAMETER; else if (isspace(ch)) ; else if (ch == '\b') parser.push(TToken(TToken::GDELETE)); + else if (ch == -1) state = ACCEPT; else transaction(ch, ACCEPT); break; case PARAMETER: - parser.push(TToken(TToken::PARAMETER, ch)); - state = ACCEPT; + if (ch == -1) error(); + else + { + parser.push(TToken(TToken::PARAMETER, ch)); + state = ACCEPT; + } break; default: assert(0); diff --git a/helm/DEVEL/mathml_editor/src/TPushLexer.hh b/helm/DEVEL/mathml_editor/src/TPushLexer.hh index ff61561ec..6fd212a3d 100644 --- a/helm/DEVEL/mathml_editor/src/TPushLexer.hh +++ b/helm/DEVEL/mathml_editor/src/TPushLexer.hh @@ -14,6 +14,7 @@ public: virtual void push(char); virtual void reset(void); + virtual void flush(void); virtual bool error(void) const; private: diff --git a/helm/DEVEL/mathml_editor/src/TPushParser.cc b/helm/DEVEL/mathml_editor/src/TPushParser.cc index a063668e0..d3dd893d7 100644 --- a/helm/DEVEL/mathml_editor/src/TPushParser.cc +++ b/helm/DEVEL/mathml_editor/src/TPushParser.cc @@ -68,12 +68,16 @@ 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")); TNode table = row.parent(); assert(table); advance(table); + + // now we have to pop the associated frame in the stack + frames.pop(); } else if (parent && parent.isG() && !parent.hasId() && parent.parent()) { @@ -452,9 +456,13 @@ TPushParser::do_control(const std::string& name) if (parent.isG()) { frames.push(Frame(entry)); - cout << "do_control: valore di pos del frame inserito " << frames.top().pos << endl; + + if (entry.table) + cout << "do_control: we have a table" << endl; + if (entry.paramDelimited(0)) { + cout << "do_control: it'a MACRO with delimited first argument" << endl; TNode g = doc.createG(); m.append(g); g.append(cursor); @@ -532,7 +540,7 @@ TPushParser::gdelete_prev() prev.append(cursor); do_gdelete(); } - else if (prev.isC()) + else if (prev.isC() && dictionary.find(prev["name"]).cls != TDictionary::UNDEFINED) { const TDictionary::Entry& entry = dictionary.find(prev["name"]); @@ -581,8 +589,11 @@ TPushParser::gdelete_prev() prev.last().append(cursor); // i have to push a frame with a correct value of pos Frame frame(entry); - frame.pos = entry.pattern.size() - 2; + assert(entry.previousParam(entry.pattern.size()) != entry.pattern.size()); + unsigned sequence_length = entry.pattern.size() - entry.previousParam(entry.pattern.size()) - 1; + frame.pos = entry.pattern.size() - sequence_length - 1; frames.push(frame); + cout << "gdelete_prev: i have inserted a frame, it's pos is: " << frames.top().pos << endl; } else { @@ -591,17 +602,32 @@ TPushParser::gdelete_prev() // remove it cout << "gdelete_prev: i try to remove the last argumet of the MACRO" << endl; cursor.remove(); - prev.append(cursor); // now prev is the cursor's parent + if (entry.table == 1 && prev.last().is("row")) + { + // in this case 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); Frame frame(entry); frame.pos = entry.pattern.size(); frames.push(frame); - gdelete_prev(); + if (cursor.prev()) gdelete_prev(); + else do_gdelete(); } } } + else if (dictionary.find(prev["name"]).cls == TDictionary::UNDEFINED) + { + // The user wants to delete an undefined element. + // I suppose that he (or she) wants to delete it all + cursor.remove(); + prev.replace(cursor); + } else { // not handled @@ -758,14 +784,20 @@ TPushParser::do_gdelete() TNode uncle = parent.prev(); if (uncle.isG() && !uncle.hasId()) { - // the cursor's parent is a phantom group, so it was a - // delimited argument of the MACRO and the corrisponding - // delimeter is inserted. So, the action of deleting means - // removing this delimeter + // the cursor's uncle is a phantom group, so it was a + // delimited argument of the MACRO and the corrisponding sequence of + // delimeters is inserted. So, the action of deleting means + // removing this sequence + 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); - frame.pos -= 2; } else { @@ -797,6 +829,65 @@ TPushParser::do_gdelete() } } + else if (gfuther.is("cell")) + { + // being here means that there is a frame in the stack + // associated to the "table" + cout << "do_gdelete: i have to delete a cell" << endl; + assert(!frames.empty()); + assert(frames.top().pos == 1); + assert(frames.top().entry.table == 1); + // a cell MUST be child of row element, which in turn MUST be child of an element + // havin attribute table. + assert(gfuther.parent() && gfuther.parent().is("row") && gfuther.parent().parent()); + TNode row = gfuther.parent(); + + // in this case the cell has no element, so the user wants to delete this cell. + TNode prev_cell = gfuther.prev(); + + cursor.remove(); + parent.remove(); + gfuther.remove(); + // now the cell no longer exists + + if (!prev_cell) + { + // i this case, the cell is the only cell in the row. + // So, i assume that the user wants to delete the entire row. + TNode table = row.parent(); + TNode prev_row = row.prev(); + row.remove(); + if (!prev_row) + { + // the row was the only child of the table. + // I think 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 other rows (one at least) + 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); + } + } + 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); + } + } } else // the grand futher is math { @@ -848,12 +939,16 @@ TPushParser::do_gdelete() if (prev.isG() && !prev.hasId()) { // in this case we have to append the cursor - // to the prev and decrement frame.pos of two units - // because the prev is a delimited argument and the - // delimiter is inserted. So we ideally remove this delimiter + // to the prev and decrement frame.pos of the length of + // delimiters sequence that delimitates the preceding argument. + // So we ideally remove this sequence + assert(frame.pos > 1); cursor.remove(); prev.append(cursor); - frame.pos -=2; + assert(frame.entry.previousParam(frame.pos) != frame.entry.pattern.size()); + unsigned sequence_length = frame.pos - frame.entry.previousParam(frame.pos) - 1; + assert(sequence_length); + frame.pos = frame.pos - sequence_length - 1; } else { @@ -867,14 +962,14 @@ TPushParser::do_gdelete() { // being here means that a prev MUST exist // and that there is only an element preceding the cursor. - // A sp's (or sb's) MUST NOT be a MACRO + // The sp's (or sb's) parent MUST NOT be a MACRO assert(parent.size() == 2); assert(parent.parent() && !parent.parent().isC()); TNode prev = cursor.prev(); cursor.remove(); - if (prev.isG() && !prev.hasId() && (prev.size() == 0)) + if (prev.isG() /*&& !prev.hasId()*/ && (prev.size() == 0)) { prev.remove(); parent.replace(cursor); @@ -900,7 +995,7 @@ TPushParser::do_gdelete() } else { - // the cursro has no parent!!! + // the cursor has no parent!!! // do nothing? // emit an error? and if we want to emit an error, in which way? } @@ -941,132 +1036,160 @@ TPushParser::push(const TToken& token) cout << "push: i have to process a token with category member = GDELETE" << endl; process(token); } - else + else if ((doc.root().first() && doc.root().first().is("math")) || token.category == TToken::SHIFT) { - 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) + 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 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)) - { - // 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); + // 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, + // so 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(); + frame.pos++; + + if (frame.pos == frame.entry.pattern.size()) + { + // This token has completed the entry + frames.pop(); + advance(parent.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 + { + // 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 correctly inserted a portion of this + // sequence, but 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 + // If we want to emit an error, we shlould implement a class, that handle + // the error. + // At the moment, the error is printed to screen + cout << "push: it's not the correct delimiter...you have to type " << frame.entry.pattern[frame.pos + 1].value << endl; + } + 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 { - // Delimiter mismatch. Since we are parsing a delimited - // argument we just process the token - process(token); + // 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 - { - // 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()); - - cout << "push: il valore di pos del frame inserito prima e': " << frame.pos << endl; - 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) + } + 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()); + + cout << "push: there is a frame with pos " << frame.pos << endl; + if (frame.entry.pattern[frame.pos].category == TToken::PARAMETER) { - // We need to increase the frame position here, becase inside - // process the function advance will be called. At that point - // it will be important for the parser to know that the entry - // has been completed in order to place the cursor correctly - // in the next position - frame.pos++; - process(token); + // As by the TeX parsing rules of undelimited parameters, + // empty spaces are ignored + if (token.category != TToken::SPACE) + { + // We need to increase the frame position here, becase inside + // process the function advance will be called. At that point + // it will be important for the parser to know that the entry + // has been completed in order to place the cursor correctly + // in the next position + frame.pos++; + 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)) + else if (frame.entry.pattern[frame.pos] == token) { - // 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); + // 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 + advance(parent); } else - advance(parent); - } - else - { - // There is a mismatch. Emit an error and ignore the token? - cerr << "ERROR: token ignored: " << token.value << " (cat: " << token.category << ")" << endl; - } + { + // There is a mismatch. Emit an error and ignore the token? + cerr << "ERROR: token ignored: " << token.value << " (cat: " << token.category << ")" << endl; + } - } - else - process(token); + } + else + process(token); + else + { + cout << "ignored token" << endl; + } + + if (listener) listener->callback(doc); //it shoul be repristened if you remove the comment in the else above + + } // this end corresponds to the if ((doc.root().first() && doc.root().first().is("math")) || token.category == TToken::SHIFT) else { - cout << "ignored token" << endl; + // there is no math element + // In this case, i think we have to emit an error + cout << "push: ignored token...you have to enter in math mode...insert $" << endl; } - } // this end corresponds to the else of the if (token.category == GDELETE) - if (listener) listener->callback(doc); + //if (listener) listener->callback(doc); if (frames.empty()) cout << "stack vuoto" << endl; else cout << "stack non vuoto" << endl; diff --git a/helm/DEVEL/mathml_editor/src/TTokenizer.cc b/helm/DEVEL/mathml_editor/src/TTokenizer.cc index cf74c1d4f..93cadefd7 100644 --- a/helm/DEVEL/mathml_editor/src/TTokenizer.cc +++ b/helm/DEVEL/mathml_editor/src/TTokenizer.cc @@ -14,7 +14,8 @@ TTokenizer::tokenize(const std::string& s) p != s.end(); p++) lexer.push(*p); - //lexer.push('\n'); + + lexer.flush(); std::vector res; res.reserve(tokens.size()); diff --git a/helm/DEVEL/mathml_editor/test/editor.cc b/helm/DEVEL/mathml_editor/test/editor.cc index 8ee408a3d..e12700e3a 100644 --- a/helm/DEVEL/mathml_editor/test/editor.cc +++ b/helm/DEVEL/mathml_editor/test/editor.cc @@ -84,6 +84,7 @@ public: if (result) dirtyId.push_back(std::make_pair(DOM::GdomeString("id"), DOM::GdomeString("'" + std::string(dirty["id"]) + "'"))); + DOM::Document res = style.apply(doc.document(), dirtyId); assert(res); style.save(doc.document(), stdout); diff --git a/helm/DEVEL/mathml_editor/xsl/tml-mmlp.xsl b/helm/DEVEL/mathml_editor/xsl/tml-mmlp.xsl index 69de8c52e..034c5073a 100644 --- a/helm/DEVEL/mathml_editor/xsl/tml-mmlp.xsl +++ b/helm/DEVEL/mathml_editor/xsl/tml-mmlp.xsl @@ -946,4 +946,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 2.39.2