mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-11 05:07:00 -04:00
Partially working text editor
git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@99 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
@@ -33,7 +33,7 @@ void ActionStack::add(Action* action, bool allow_merge) {
|
||||
FOR_EACH(a, redo_actions) delete a;
|
||||
redo_actions.clear();
|
||||
// try to merge?
|
||||
if (allow_merge && !undo_actions.empty() && undo_actions.back()->merge(action)) {
|
||||
if (allow_merge && !undo_actions.empty() && undo_actions.back()->merge(*action)) {
|
||||
// merged with top undo action
|
||||
delete action;
|
||||
} else {
|
||||
|
||||
@@ -40,7 +40,7 @@ class Action {
|
||||
/** Either: return false and do nothing
|
||||
* Or: return true and change this action to incorporate both actions
|
||||
*/
|
||||
virtual bool merge(const Action* action) { return false; }
|
||||
virtual bool merge(const Action& action) { return false; }
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Action listeners
|
||||
|
||||
@@ -73,9 +73,12 @@ class Rotation {
|
||||
|
||||
friend class Rotater;
|
||||
|
||||
public:
|
||||
/// Is the rotation sideways (90 or 270 degrees)?
|
||||
// Note: angle & 2 == 0 for angle in {0, 180} and != 0 for angle in {90, 270)
|
||||
inline bool sideways() const { return (angle & 2) != 0; }
|
||||
|
||||
protected:
|
||||
/// Is the x axis 'reversed' (after turning sideways)?
|
||||
inline bool revX() const { return angle >= 180; }
|
||||
/// Is the y axis 'reversed' (after turning sideways)?
|
||||
|
||||
@@ -62,6 +62,10 @@ String trim_left(const String& s) {
|
||||
}
|
||||
}
|
||||
|
||||
String substr_replace(const String& input, size_t start, size_t end, const String& replacement) {
|
||||
return input.substr(0,start) + replacement + input.substr(end);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Words
|
||||
|
||||
String last_word(const String& s) {
|
||||
|
||||
@@ -81,6 +81,9 @@ String trim(const String&);
|
||||
/// Remove whitespace from the start of a string
|
||||
String trim_left(const String&);
|
||||
|
||||
/// Replace the substring [start...end) of 'input' with 'replacement'
|
||||
String substr_replace(const String& input, size_t start, size_t end, const String& replacement);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Words
|
||||
|
||||
/// Returns the last word in a string
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <util/tagged_string.hpp>
|
||||
#include <stack>
|
||||
|
||||
// ----------------------------------------------------------------------------- : Conversion to/from normal string
|
||||
|
||||
@@ -41,6 +42,44 @@ String escape(const String& str) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
String fix_old_tags(const String& str) {
|
||||
String ret; ret.reserve(str.size());
|
||||
stack<String> tags;
|
||||
bool intag = false;
|
||||
// invariant : intag => !tags.empty()
|
||||
for (size_t i = 0 ; i < str.size() ; ++i) {
|
||||
Char c = str.GetChar(i);
|
||||
if (is_substr(str, i, _("</>"))) {
|
||||
i += 2;
|
||||
// old style close tag, replace by the correct tag type
|
||||
if (!tags.empty()) {
|
||||
// need a close tag?
|
||||
if (!tags.top().empty()) {
|
||||
ret += _("</") + tags.top() + _(">");
|
||||
}
|
||||
tags.pop();
|
||||
}
|
||||
intag = false;
|
||||
} else {
|
||||
ret += c;
|
||||
if (c==_('<')) {
|
||||
intag = true;
|
||||
tags.push(wxEmptyString);
|
||||
} else if (c==_('>') && intag) {
|
||||
intag = false;
|
||||
if (!starts_with(tags.top(), _("kw")) && !starts_with(tags.top(), _("atom"))) {
|
||||
// only keep keyword related stuff
|
||||
ret.resize(ret.size() - tags.top().size() - 2); // remove from output
|
||||
tags.top() = wxEmptyString;
|
||||
}
|
||||
} else if (intag) {
|
||||
tags.top() += c;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Finding tags
|
||||
|
||||
size_t skip_tag(const String& str, size_t start) {
|
||||
@@ -49,5 +88,179 @@ size_t skip_tag(const String& str, size_t start) {
|
||||
return end == String::npos ? String::npos : end + 1;
|
||||
}
|
||||
|
||||
size_t match_close_tag(const String& str, size_t start) {
|
||||
String tag = tag_type_at(str, start);
|
||||
String ctag = _("/") + tag;
|
||||
size_t size = str.size();
|
||||
int taglevel = 1;
|
||||
for (size_t pos = start + tag.size() + 2 ; pos < size ; ++pos) {
|
||||
Char c = str.GetChar(pos);
|
||||
if (c == _('<')) {
|
||||
if (is_substr(str, pos + 1, tag)) {
|
||||
++taglevel;
|
||||
pos += tag.size() + 1;
|
||||
} else if (is_substr(str, pos + 1, ctag)) {
|
||||
--taglevel; // close tag
|
||||
if (taglevel == 0) return pos;
|
||||
pos += ctag.size() + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return String::npos;
|
||||
}
|
||||
|
||||
size_t last_start_tag_before(const String& str, const String& tag, size_t start) {
|
||||
start = min(str.size(), start);
|
||||
for (size_t pos = start ; pos > 0 ; --pos) {
|
||||
if (is_substr(str, pos - 1, tag)) {
|
||||
return pos - 1;
|
||||
}
|
||||
}
|
||||
return String::npos;
|
||||
}
|
||||
|
||||
size_t in_tag(const String& str, const String& tag, size_t start, size_t end) {
|
||||
if (start > end) swap(start, end);
|
||||
size_t pos = last_start_tag_before(str, tag, start);
|
||||
if (pos == String::npos) return String::npos; // no tag found before start
|
||||
size_t posE = match_close_tag(str, pos);
|
||||
if (posE < end) return String::npos; // the tag ends before end
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
String tag_at(const String& str, size_t pos) {
|
||||
size_t end = str.find_first_of(_(">"), pos);
|
||||
if (end == String::npos) return wxEmptyString;
|
||||
return str.substr(pos + 1, end - pos - 1);
|
||||
}
|
||||
|
||||
String tag_type_at(const String& str, size_t pos) {
|
||||
size_t end = str.find_first_of(_(">-"), pos);
|
||||
if (end == String::npos) return wxEmptyString;
|
||||
return str.substr(pos + 1, end - pos - 1);
|
||||
}
|
||||
|
||||
String close_tag(const String& tag) {
|
||||
if (tag.size() < 1) return _("</>");
|
||||
else return _("</") + tag.substr(1);
|
||||
}
|
||||
|
||||
String anti_tag(const String& tag) {
|
||||
if (!tag.empty() && tag.GetChar(0) == _('/')) return _("<") + tag.substr(1) + _(">");
|
||||
else return _("</") + tag + _(">");
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Global operations
|
||||
// ----------------------------------------------------------------------------- : Updates
|
||||
|
||||
/// Return all open or close tags in the given range from a string
|
||||
/** for example:
|
||||
* if close_tags == false, "text<tag>text</tag>text" --> "<tag>"
|
||||
* if close_tags == true, "text<tag>text</tag>text" --> "</tag>"
|
||||
*/
|
||||
String get_tags(const String& str, size_t start, size_t end, bool close_tags) {
|
||||
String ret;
|
||||
bool intag = false;
|
||||
bool keeptag = false;
|
||||
for (size_t i = start ; i < end ; ++i) {
|
||||
Char c = str.GetChar(i);
|
||||
if (c == _('<') && !intag) {
|
||||
intag = true;
|
||||
// is this tag an open tag?
|
||||
if (i + 1 < end && (str.GetChar(i + 1) == _('/')) == close_tags) {
|
||||
keeptag = true;
|
||||
}
|
||||
}
|
||||
if (intag && keeptag) ret += c;
|
||||
if (c == _('>')) intag = false;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
String tagged_substr_replace(const String& input, size_t start, size_t end, const String& replacement) {
|
||||
assert(start <= end);
|
||||
size_t size = input.size();
|
||||
String ret; ret.reserve(size + replacement.size() - (end - start)); // estimated size
|
||||
return simplify_tagged(
|
||||
substr_replace(input, start, end,
|
||||
get_tags(input, start, end, true) + // close tags
|
||||
escape(replacement) +
|
||||
get_tags(input, start, end, false) // open tags
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Simplification
|
||||
|
||||
String simplify_tagged(const String& str) {
|
||||
return simplify_tagged_overlap(simplify_tagged_merge(str));
|
||||
}
|
||||
|
||||
// Add a tag to a stack of tags, try to cancel it out
|
||||
// If </tag> is in stack remove it and returns true
|
||||
// otherwise appends <tag> and returns fales
|
||||
// (where </tag> is the negation of tag)
|
||||
bool add_or_cancel_tag(const String& tag, String& stack) {
|
||||
String anti = anti_tag(tag);
|
||||
size_t pos = stack.find(anti);
|
||||
if (pos == String::npos) {
|
||||
stack += _("<") + tag + _(">");
|
||||
return false;
|
||||
} else {
|
||||
// cancel out with anti tag
|
||||
stack = stack.substr(0, pos) + stack.substr(pos + anti.size());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
String simplify_tagged_merge(const String& str) {
|
||||
String ret; ret.reserve(str.size());
|
||||
String waiting_tags; // tags that are waiting to be written to the output
|
||||
size_t size = str.size();
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
Char c = str.GetChar(i);
|
||||
if (c == _('<')) {
|
||||
String tag = tag_at(str, i);
|
||||
add_or_cancel_tag(tag, waiting_tags);
|
||||
i += tag.size() + 1;
|
||||
} else {
|
||||
ret += waiting_tags;
|
||||
waiting_tags.clear();
|
||||
ret += c;
|
||||
}
|
||||
}
|
||||
return ret + waiting_tags;
|
||||
}
|
||||
|
||||
String simplify_tagged_overlap(const String& str) {
|
||||
String ret; ret.reserve(str.size());
|
||||
String open_tags; // tags we are in
|
||||
size_t size = str.size();
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
Char c = str.GetChar(i);
|
||||
if (c == _('<')) {
|
||||
String tag = tag_at(str, i);
|
||||
if (starts_with(tag, _("b")) || starts_with(tag, _("i")) || starts_with(tag, _("sym")) ||
|
||||
starts_with(tag, _("/b")) || starts_with(tag, _("/i")) || starts_with(tag, _("/sym"))) {
|
||||
// optimize this tag
|
||||
if (open_tags.find(_("<") + tag + _(">")) == String::npos) {
|
||||
// we are not already inside this tag
|
||||
add_or_cancel_tag(tag, open_tags);
|
||||
if (open_tags.find(anti_tag(tag)) != String::npos) {
|
||||
// still not canceled out
|
||||
i += tag.size() + 2;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// skip this tag, doubling it has no effect
|
||||
i += tag.size() + 2;
|
||||
add_or_cancel_tag(tag, open_tags);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
ret += c;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -46,10 +46,17 @@ String fix_old_tags(const String&);
|
||||
size_t skip_tag(const String& str, size_t start);
|
||||
|
||||
/// Find the position of the closing tag matching the tag at start
|
||||
/** If not found returns String::npos
|
||||
*/
|
||||
/** If not found returns String::npos */
|
||||
size_t match_close_tag(const String& str, size_t start);
|
||||
|
||||
/// Find the last start tag before position start
|
||||
/** If not found returns String::npos */
|
||||
size_t last_start_tag_before(const String& str, const String& tag, size_t start);
|
||||
|
||||
/// Is the given range entirely contained in a given tag?
|
||||
/** If so: return the start position of that tag, otherwise returns String::npos */
|
||||
size_t in_tag(const String& str, const String& tag, size_t start, size_t end);
|
||||
|
||||
/// Return the tag at the given position (without the <>)
|
||||
String tag_at(const String& str, size_t pos);
|
||||
|
||||
@@ -80,6 +87,17 @@ String remove_tag_exact(const String& str, const String& tag);
|
||||
*/
|
||||
String remove_tag_contents(const String& str, const String& tag);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Updates
|
||||
|
||||
/// Replace a subsection of 'input' with 'replacement'.
|
||||
/** The section to replace is indicated by [start...end).
|
||||
* This function makes sure tags still match. It also attempts to cancel out tags.
|
||||
* This means that when removing "<x>a</x>" nothing is left,
|
||||
* but with input "<x>a" -> "<x>" and "</>a" -> "</>".
|
||||
* Escapes the replacement, i.e. all < in become \1.
|
||||
*/
|
||||
String tagged_substr_replace(const String& input, size_t start, size_t end, const String& replacement);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Simplification
|
||||
|
||||
/// Verify that a string is correctly tagged, if it is not, change it so it is
|
||||
@@ -95,10 +113,14 @@ String verify_tagged(const String& str);
|
||||
*/
|
||||
String simplify_tagged(const String& str);
|
||||
|
||||
/// Simplify a tagged string by merging adjecent open/close tags "<tag></tag>" --> ""
|
||||
/// Simplify a tagged string by merging adjecent open/close tags
|
||||
/** e.g. "<tag></tag>" --> ""
|
||||
*/
|
||||
String simplify_tagged_merge(const String& str);
|
||||
|
||||
/// Simplify overlapping formatting tags
|
||||
/** e.g. "<i>blah<i>blah</i>blah</i>" -> "<i>blahblahblah</i>"
|
||||
*/
|
||||
String simplify_tagged_overlap(const String& str);
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
Reference in New Issue
Block a user