/* libobby - Network text editing library
* Copyright (C) 2005, 2006 0x539 dev group
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "text.hpp"
namespace
{
const obby::text::size_type CHUNK_INIT =
~static_cast<obby::text::size_type>(0);
inline obby::text::size_type CHUNK_SIZE(obby::text::size_type size)
{
return size == obby::text::npos ? CHUNK_INIT : size;
}
// find_chunk implementation to avoid code duplication for
// iterator and const_iterator versions
template<typename List, typename Iter>
Iter find_chunk(List list, obby::text::size_type& pos)
{
for(Iter it = list.begin(); it != list.end(); ++ it)
{
if( (*it)->get_length() > pos)
return it;
else
pos -= (*it)->get_length();
}
if(pos == 0) return list.end();
throw std::logic_error(
"obby::text::find_chunk:\n"
"Requested position exceeds text's size"
);
}
}
obby::text::chunk::chunk(const chunk& other):
m_text(other.m_text), m_author(other.m_author)
{
}
obby::text::chunk::chunk(const string_type& string,
const user* author):
m_text(string),
m_author(author)
{
}
obby::text::chunk::chunk(const net6::packet& pack,
unsigned int& index,
const user_table& table):
m_text(pack.get_param(index + 0).as<std::string>() ),
m_author(
pack.get_param(index + 1).as<const user*>(
::serialise::hex_context_from<const user*>(table)
)
)
{
index += 2;
}
obby::text::chunk::chunk(const serialise::object& obj,
const user_table& table):
m_text(obj.get_required_attribute("content").as<std::string>() ),
m_author(
obj.get_required_attribute("author").as<const user*>(
::serialise::default_context_from<const user*>(table)
)
)
{
}
void obby::text::chunk::serialise(serialise::object& obj) const
{
obj.add_attribute("content").set_value(m_text);
obj.add_attribute("author").set_value(m_author);
}
void obby::text::chunk::append_packet(net6::packet& pack) const
{
pack << m_text << m_author;
}
void obby::text::chunk::prepend(const string_type& text)
{
m_text.insert(0, text);
}
void obby::text::chunk::append(const string_type& text)
{
m_text.append(text);
}
void obby::text::chunk::insert(size_type pos, const string_type& text)
{
m_text.insert(pos, text);
}
void obby::text::chunk::erase(size_type pos, size_type len)
{
m_text.erase(pos, len);
}
const obby::text::string_type& obby::text::chunk::get_text() const
{
return m_text;
}
const obby::user* obby::text::chunk::get_author() const
{
return m_author;
}
obby::text::size_type obby::text::chunk::get_length() const
{
return m_text.length();
}
obby::text::text(size_type initial_chunk_size):
m_max_chunk(CHUNK_SIZE(initial_chunk_size) )
{
}
obby::text::text(const text& other):
m_max_chunk(other.m_max_chunk)
{
for(list_type::const_iterator iter = other.m_chunks.begin();
iter != other.m_chunks.end();
++ iter)
{
m_chunks.push_back(new chunk(**iter) );
}
}
obby::text::text(const string_type& string,
const user* author,
size_type initial_chunk_size):
m_max_chunk(CHUNK_SIZE(initial_chunk_size) )
{
for(size_type n = 0; n < string.length(); ++ n)
{
size_type len = std::min(string.length() - n, m_max_chunk);
m_chunks.push_back(new chunk(string.substr(n, len), author) );
}
}
obby::text::text(const net6::packet& pack,
unsigned int& index,
const user_table& table):
m_max_chunk(CHUNK_INIT)
{
unsigned int count = pack.get_param(index ++).as<unsigned int>();
for(unsigned int i = 0; i < count; ++ i)
m_chunks.push_back(new chunk(pack, index, table) );
}
obby::text::text(const serialise::object& obj,
const user_table& table):
m_max_chunk(CHUNK_INIT)
{
for(serialise::object::child_iterator iter = obj.children_begin();
iter != obj.children_end();
++ iter)
{
if(iter->get_name() == "chunk")
{
m_chunks.push_back(new chunk(*iter, table) );
}
else
{
// TODO: unexpected_child_error
format_string str(_("Unexpected child node: '%0%'") );
str << iter->get_name();
throw serialise::error(str.str(), iter->get_line() );
}
}
}
obby::text::~text()
{
clear();
}
obby::text& obby::text::operator=(const text& other)
{
if(&other == this) return *this;
clear();
m_max_chunk = other.m_max_chunk;
for(list_type::const_iterator iter = other.m_chunks.begin();
iter != other.m_chunks.end();
++ iter)
{
m_chunks.push_back(new chunk(**iter) );
}
return *this;
}
void obby::text::serialise(serialise::object& obj) const
{
for(list_type::const_iterator it = m_chunks.begin();
it != m_chunks.end();
++ it)
{
serialise::object& part = obj.add_child();
part.set_name("chunk");
(*it)->serialise(part);
}
}
void obby::text::append_packet(net6::packet& pack) const
{
pack << m_chunks.size();
for(list_type::const_iterator it = m_chunks.begin();
it != m_chunks.end();
++ it)
{
(*it)->append_packet(pack);
}
}
void obby::text::clear()
{
for(list_type::iterator it = m_chunks.begin();
it != m_chunks.end();
++ it)
{
delete *it;
}
m_chunks.clear();
}
obby::text obby::text::substr(size_type pos, size_type len) const
{
text new_text;
list_type::const_iterator iter = find_chunk(pos);
chunk* prev_chunk = NULL;
while( (len == npos || len > 0) && (iter != m_chunks.end()) )
{
chunk* cur_chunk = *iter;
size_type count = cur_chunk->get_length() - pos;
if(len != npos)
{
count = std::min(count, len);
len -= count;
}
if(prev_chunk != NULL &&
prev_chunk->get_author() == cur_chunk->get_author() &&
prev_chunk->get_length() + cur_chunk->get_length() <=
m_max_chunk)
{
prev_chunk->append(
cur_chunk->get_text().substr(pos, count)
);
}
else
{
prev_chunk = new chunk(
cur_chunk->get_text().substr(pos, count),
cur_chunk->get_author()
);
new_text.m_chunks.push_back(prev_chunk);
}
++ iter; pos = 0;
}
if(len > 0 && len != npos)
{
throw std::logic_error(
"obby::text::substr:\n"
"len is out or range"
);
}
return new_text;
}
void obby::text::insert(size_type pos,
const string_type& str,
const user* author)
{
list_type::iterator ins_pos = find_chunk(pos);
insert_chunk(ins_pos, pos, str, author);
}
void obby::text::insert(size_type pos,
const text& str)
{
list_type::iterator ins_pos = find_chunk(pos);
for(list_type::const_iterator it = str.m_chunks.begin();
it != str.m_chunks.end();
++ it)
{
ins_pos = insert_chunk(
ins_pos,
pos,
(*it)->get_text(),
(*it)->get_author()
);
}
}
void obby::text::erase(size_type pos, size_type len)
{
list_type::iterator ers_pos = find_chunk(pos);
list_type::iterator first_chunk = ers_pos;
size_type first_pos = pos;
// Remember first chunk position that will not be removed.
// After having erased a chunk in the middle, something may have
// been mergen with this first chunk that should have been deleted.
// We watch it and remove merged stuff if the chunk grew.
if(first_pos == 0 && first_chunk != m_chunks.begin() )
{
-- first_chunk;
first_pos = (*first_chunk)->get_length();
}
while( (len == npos || len > 0) && ers_pos != m_chunks.end() )
{
size_type count = (*ers_pos)->get_length() - pos;
if(len != npos)
{
count = std::min(count, len);
len -= count;
}
ers_pos = erase_chunk(ers_pos, pos, count);
if(first_pos > 0 && (*first_chunk)->get_length() > first_pos)
{
ers_pos = first_chunk;
pos = first_pos;
}
else
{
pos = 0;
}
}
if(len != npos && len > 0)
{
throw std::logic_error(
"obby::text::erase:\n"
"len is out of range"
);
}
}
void obby::text::append(const string_type& str,
const user* author)
{
chunk* last_chunk = NULL;
if(!m_chunks.empty() ) last_chunk = *m_chunks.rbegin();
size_type pos = 0;
// Merge beginning of str with last chunk if possible
if(last_chunk != NULL &&
last_chunk->get_author() == author &&
last_chunk->get_length() < m_max_chunk)
{
pos = std::min(
m_max_chunk - last_chunk->get_length(),
str.length()
);
last_chunk->append(str.substr(0, pos) );
}
// Append rest of string
for(; pos < str.length(); pos += m_max_chunk)
{
size_type count = std::min(str.length() - pos, m_max_chunk);
m_chunks.push_back(new chunk(str.substr(pos, count), author) );
}
}
void obby::text::append(const text& str)
{
// Simply append all chunks of str
for(list_type::const_iterator it = str.m_chunks.begin();
it != str.m_chunks.end();
++ it)
{
append( (*it)->get_text(), (*it)->get_author() );
}
}
void obby::text::prepend(const string_type& str,
const user* author)
{
chunk* first_chunk = NULL;
if(!m_chunks.empty() ) first_chunk = *m_chunks.begin();
size_type len = str.length();
// Prepend end of str to first chunk if possible
if(first_chunk != NULL &&
first_chunk->get_author() == author &&
first_chunk->get_length() < m_max_chunk)
{
size_type count = std::min(
m_max_chunk - first_chunk->get_length(),
len
);
len -= count;
first_chunk->prepend(str.substr(len, count) );
}
// Insert chunks before for the rest of str
while(len > 0)
{
size_type count = std::min(
len,
m_max_chunk
);
len -= count;
m_chunks.push_front(new chunk(str.substr(len, count), author));
}
}
void obby::text::prepend(const text& str)
{
for(list_type::const_reverse_iterator it = str.m_chunks.rbegin();
it != str.m_chunks.rend();
++ it)
{
prepend( (*it)->get_text(), (*it)->get_author() );
}
}
obby::text::size_type obby::text::length() const
{
// TODO: Cache this value?
size_type len = 0;
for(list_type::const_iterator it = m_chunks.begin();
it != m_chunks.end();
++ it)
{
len += (*it)->get_length();
}
return len;
}
bool obby::text::operator==(const text& other) const
{
return compare(other) == EQUAL_OWNERMATCH;
}
bool obby::text::operator!=(const text& other) const
{
return compare(other) != EQUAL_OWNERMATCH;
}
bool obby::text::operator<(const text& other) const
{
return compare(other) == LESS;
}
bool obby::text::operator>(const text& other) const
{
return compare(other) == GREATER;
}
bool obby::text::operator<=(const text& other) const
{
return compare(other) != GREATER;
}
bool obby::text::operator>=(const text& other) const
{
return compare(other) != LESS;
}
bool obby::text::operator==(const string_type& other) const
{
return compare(other) == EQUAL;
}
bool obby::text::operator!=(const string_type& other) const
{
return compare(other) != EQUAL;
}
bool obby::text::operator<(const string_type& other) const
{
return compare(other) == LESS;
}
bool obby::text::operator>(const string_type& other) const
{
return compare(other) == GREATER;
}
bool obby::text::operator<=(const string_type& other) const
{
return compare(other) != GREATER;
}
bool obby::text::operator>=(const string_type& other) const
{
return compare(other) != LESS;
}
bool obby::text::empty() const
{
return m_chunks.empty();
}
obby::text::chunk_iterator obby::text::chunk_begin() const
{
return chunk_iterator(m_chunks.begin() );
}
obby::text::chunk_iterator obby::text::chunk_end() const
{
return chunk_iterator(m_chunks.end() );
}
void obby::text::set_max_chunk_size(size_type max_chunk)
{
m_max_chunk = max_chunk;
if(m_chunks.empty() ) return;
// Merge and/or split current chunks
list_type::iterator next = m_chunks.begin(); ++ next;
for(list_type::iterator it = m_chunks.begin();
it != m_chunks.end();
++ it, ++ next)
{
chunk* cur_chunk = *it;
chunk* next_chunk = NULL;
if(next != m_chunks.end() ) next_chunk = *next;
// Split current chunk if necessary
if(cur_chunk->get_length() > m_max_chunk)
{
size_type pos = m_max_chunk;
while(cur_chunk->get_length() - pos > 0)
{
// Merge with next if possible
if(next_chunk != NULL &&
next_chunk->get_author() ==
cur_chunk->get_author() &&
cur_chunk->get_length() - pos +
next_chunk->get_length() <= m_max_chunk)
{
// Note that this could also be done
// in the merging process below but
// then we would split the chunk up
// just to merge it then...
next_chunk->prepend(
cur_chunk->get_text().substr(
pos
)
);
pos += (cur_chunk->get_length() - pos);
}
// Split otherwise
else
{
size_type len = std::min(
cur_chunk->get_length() - pos,
m_max_chunk
);
const std::string& text =
cur_chunk->get_text();
it = m_chunks.insert(
next,
new chunk(
text.substr(
pos,
len
),
cur_chunk->get_author()
)
);
pos += len;
}
}
// Remove splitted/merged stuff from current one
cur_chunk->erase(m_max_chunk);
cur_chunk = *it;
}
// Merge chunk with next
else if(next_chunk != NULL &&
cur_chunk->get_author() == next_chunk->get_author() &&
cur_chunk->get_length() + next_chunk->get_length() <=
m_max_chunk)
{
cur_chunk->append(next_chunk->get_text() );
delete next_chunk;
next = m_chunks.erase(next);
}
}
}
obby::text::operator string_type() const
{
string_type str;
str.reserve(length() );
for(list_type::const_iterator it = m_chunks.begin();
it != m_chunks.end();
++ it)
{
str.append( (*it)->get_text() );
}
return str;
}
obby::text::list_type::iterator
obby::text::find_chunk(size_type& pos)
{
return ::find_chunk<list_type&, list_type::iterator>(
m_chunks,
pos
);
}
obby::text::list_type::const_iterator
obby::text::find_chunk(size_type& pos) const
{
return ::find_chunk<const list_type&, list_type::const_iterator>(
m_chunks,
pos
);
}
obby::text::list_type::iterator
obby::text::insert_chunk(list_type::iterator chunk_it,
size_type& chunk_pos,
const string_type& str,
const user* author)
{
chunk* cur_chunk = NULL;
if(chunk_it != m_chunks.end() ) cur_chunk = *chunk_it;
list_type::iterator ins_pos = chunk_it;
// Get previous chunk
chunk* prev_chunk = NULL;
list_type::iterator prev_pos = ins_pos;
if(prev_pos != m_chunks.begin() )
{
-- prev_pos;
prev_chunk = *prev_pos;
}
// Merge with previous
if(prev_chunk != NULL &&
chunk_pos == 0 &&
author == prev_chunk->get_author() &&
str.length() + prev_chunk->get_length() <= m_max_chunk)
{
prev_chunk->append(str);
return chunk_it;
}
else if(cur_chunk == NULL)
{
// Insertion at end (no current chunk) and cannot merge with
// previous: Need to create a new one, do nothing here - this
// is done below
}
// Merge with current
else if(author == cur_chunk->get_author() &&
str.length() + cur_chunk->get_length() <= m_max_chunk)
{
cur_chunk->insert(chunk_pos, str);
chunk_pos += str.length();
return chunk_it;
}
// Insert new chunk after current chunk if str is inserted at
// end of current chunk
else if(chunk_pos == cur_chunk->get_length() )
{
++ ins_pos;
}
// Insert new chunk before current chunk if str is inserted at
// the beginning of current chunk
else if(chunk_pos > 0)
{
// Split up otherwise
chunk* new_chunk = new chunk(
cur_chunk->get_text().substr(chunk_pos),
cur_chunk->get_author()
);
cur_chunk->erase(chunk_pos);
chunk_pos = 0;
++ ins_pos;
ins_pos = m_chunks.insert(
ins_pos,
new_chunk
);
// Try to merge with both chunks - they may be smaller
// and thus fit into max chunk size
if(cur_chunk->get_author() == author)
{
if(cur_chunk->get_length() + str.length() <=
m_max_chunk)
{
cur_chunk->append(str);
chunk_pos = cur_chunk->get_length();
-- ins_pos;
return ins_pos;
}
else if(new_chunk->get_length() +
str.length() <= m_max_chunk)
{
new_chunk->prepend(str);
chunk_pos = str.length();
return ins_pos;
}
}
// If not insert another new chunk between
// the chunks split up - ins_pos points already
// to the correct position
}
// Insert one new chunk if str fits into
if(str.length() <= m_max_chunk)
{
chunk_pos = 0;
m_chunks.insert(ins_pos, new chunk(str, author) );
return ins_pos;
}
else
{
// Make multiple chunks otherwise
// TODO: Fill up previous chunk if author matches and ins_pos
// is != m_chunks.begin()
cur_chunk = ( (ins_pos == m_chunks.end()) ? NULL : *ins_pos);
for(size_type n = 0; n < str.length(); n += m_max_chunk)
{
size_type len = std::min(str.length() - n, m_max_chunk);
// Merge with next
if(cur_chunk &&
cur_chunk->get_author() == author &&
len + cur_chunk->get_length() <= m_max_chunk)
{
// Must be last chunk to insert since all
// others are m_max_chunk in size and thus
// may not be merged
cur_chunk->prepend(str.substr(n, len) );
chunk_pos = len;
return ins_pos;
}
else
{
/*result =*/ m_chunks.insert(
ins_pos,
new chunk(str.substr(n, len), author)
);
}
}
chunk_pos = 0;
return ins_pos;
}
}
obby::text::list_type::iterator
obby::text::erase_chunk(list_type::iterator chunk_it,
size_type pos,
size_type len)
{
chunk* prev_chunk = NULL;
chunk* next_chunk = NULL;
list_type::iterator prev_it = chunk_it;
if(prev_it != m_chunks.begin() ) { -- prev_it; prev_chunk = *prev_it; }
list_type::iterator next_it = chunk_it;
++ next_it;
if(next_it != m_chunks.end() ) { next_chunk = *next_it; }
chunk* cur_chunk = *chunk_it;
//if(len == npos) len = cur_chunk->get_length() - pos;
if(pos + len > cur_chunk->get_length() )
{
throw std::logic_error(
"obby::text::erase_chunk:\n"
"Chunk len exceeded"
);
}
// Complete erasure
if(len == cur_chunk->get_length() )
{
delete cur_chunk;
m_chunks.erase(chunk_it);
// Merge surrounding chunks if possible
if(next_chunk != NULL && prev_chunk != NULL &&
next_chunk->get_author() == prev_chunk->get_author() &&
next_chunk->get_length() + prev_chunk->get_length() <
m_max_chunk)
{
prev_chunk->append(next_chunk->get_text() );
delete next_chunk;
next_it = m_chunks.erase(next_it);
}
return next_it;
}
// Merge with previous
if(prev_chunk != NULL &&
prev_chunk->get_author() == cur_chunk->get_author() &&
cur_chunk->get_length() - len + prev_chunk->get_length() <
m_max_chunk)
{
if(pos > 0)
{
prev_chunk->append(
cur_chunk->get_text().substr(0, pos)
);
}
if(pos + len < cur_chunk->get_length() )
{
prev_chunk->append(
cur_chunk->get_text().substr(pos + len)
);
}
delete cur_chunk;
m_chunks.erase(chunk_it);
// Merge result with next if possible
if(next_chunk != NULL &&
prev_chunk->get_author() == next_chunk->get_author() &&
prev_chunk->get_length() + next_chunk->get_length() <=
m_max_chunk)
{
prev_chunk->append(next_chunk->get_text() );
delete next_chunk;
next_it = m_chunks.erase(next_it);
}
return next_it;
}
// Merge with next
if(next_chunk != NULL &&
next_chunk->get_author() == cur_chunk->get_author() &&
cur_chunk->get_length() - len + next_chunk->get_length() <
m_max_chunk)
{
if(pos + len < cur_chunk->get_length() )
{
next_chunk->prepend(
cur_chunk->get_text().substr(pos)
);
}
if(pos > 0)
{
next_chunk->prepend(
cur_chunk->get_text().substr(0, pos)
);
}
delete cur_chunk;
m_chunks.erase(chunk_it);
// No need to try to merge with previous since the check
// above would already have done it.
++ next_it;
return next_it;
}
// No merging possible...
cur_chunk->erase(pos, len);
return next_it;
}
obby::text::compare_result obby::text::compare(const text& other) const
{
list_type::const_iterator it1 = m_chunks.begin();
list_type::const_iterator it2 = other.m_chunks.begin();
size_type pos1 = 0, pos2 = 0;
bool author_match = true;
while(it1 != m_chunks.end() && it2 != other.m_chunks.end() )
{
// Authors do not match: Remember this for later use. If
// the strings differ, LESS or GREATER is returned; author
// match does only play a role when the text content is equal
if( (*it1)->get_author() != (*it2)->get_author() )
author_match = false;
size_type len = std::min(
(*it1)->get_length() - pos1,
(*it2)->get_length() - pos2
);
int res = (*it1)->get_text().compare(
pos1,
len,
(*it2)->get_text(),
pos2,
len
);
if(res != 0) return res < 0 ? LESS : GREATER;
pos1 += len;
pos2 += len;
if(pos1 == (*it1)->get_length() )
{ ++ it1; pos1 = 0; }
if(pos2 == (*it2)->get_length() )
{ ++ it2; pos2 = 0; }
}
// *this has more length than other
if(it1 != m_chunks.end() )
return GREATER;
// other has more length
else if(it2 != other.m_chunks.end() )
return LESS;
// both text's content are equal
else
return author_match ? EQUAL_OWNERMATCH : EQUAL;
}
obby::text::compare_result obby::text::compare(const string_type& text) const
{
size_type pos = 0;
for(list_type::const_iterator it = m_chunks.begin();
it != m_chunks.end();
++ it)
{
size_type len = (*it)->get_length();
int res = text.compare(pos, len, (*it)->get_text());
if(res != 0) return res < 0 ? LESS : GREATER;
pos += len;
}
return EQUAL;
}
syntax highlighted by Code2HTML, v. 0.9.1