/*
* TextUtils.cs - Utilities for text handling.
*
* Copyright (C) 2004 - 2011 Andreas Scherrer
*
* This 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 software 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 software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace Level9
{
///
/// Utilities for text handling.
///
public class TextUtils
{
///
/// Splits a paragraph into separate lines with a given number of
/// columns.
///
/// the paragraph string
/// the number of columns per line
/// a list of lines
public static List SplitParagraph(string paragraph, int columns)
{
return SplitParagraph(paragraph, int.MaxValue, columns);
}
///
/// Splits a paragraph into separate lines with a given number of
/// columns.
///
/// the paragraph string
/// the number of lines
/// the number of columns per line
/// a list of lines
public static List SplitParagraph(string paragraph,
int numLines, int columns)
{
List lines = new List();
if (paragraph == null || paragraph.Length == 0 || columns <= 0
|| numLines <= 0) {
return lines;
}
StringBuilder line = new StringBuilder();
int textLength = paragraph.Length;
int textEnd = textLength - 1;
int textPos = 0;
int lineStart = 0;
int wordStart = 0;
while (true) {
if ((textPos > 0 && textPos < textEnd
&& paragraph[textPos] == ' '
&& paragraph[textPos + 1] != ' ')
|| textPos == textEnd) {
wordStart = textPos + 1;
}
if (line.Length == columns || textPos == textLength) {
//
// Cut line
//
if (wordStart > lineStart && textPos > wordStart) {
int cut = textPos - wordStart;
line.Remove(line.Length - cut, cut);
textPos -= cut;
}
//
// Ignore blank at the end of a line
//
if (textPos < textEnd && paragraph[textPos] == ' ') {
textPos++;
}
//
// Add line
//
lines.Add(line.ToString().Trim());
if (lines.Count == numLines) {
break;
}
line = new StringBuilder();
lineStart = textPos;
//
// End of paragraph
//
if (textPos == textLength) {
break;
}
}
else {
line.Append(paragraph[textPos++]);
}
}
return lines;
}
///
/// Splits a list of paragraphs into separate lines with a given number
/// of columns.
///
/// the list of paragraphs
/// the number of lines
/// the number of columns per line
///
/// if set to true the paragraphs are handled from top to bottom.
/// If set to false the paragraphs are handled from bottom to top
///
/// a list of lines
public static List SplitParagraphs(
List paragraphs, int lines, int columns,
bool topDown)
{
return SplitParagraphs(paragraphs, lines, columns,
new TextPosition(0, 0), topDown);
}
///
/// Splits a list of paragraphs into separate lines with a given number
/// of columns.
///
/// the list of paragraphs
/// the number of lines
/// the number of columns per line
///
/// the offset position in the list of paragraphs (top-down mode only)
///
///
/// if set to true the paragraphs are handled from top to bottom.
/// If set to false the paragraphs are handled from bottom to top
///
/// a list of lines
public static List SplitParagraphs(
List paragraphs, int lines, int columns,
TextPosition offset, bool topDown)
{
List outputLines = new List();
if (paragraphs == null || offset == null || lines <= 0
|| columns <= 0 || offset.Paragraph >= paragraphs.Count) {
return outputLines;
}
if (topDown) {
for (int i = offset.Paragraph; i < paragraphs.Count; i++) {
int offsetCol = (i == offset.Paragraph) ? offset.Column : 0;
outputLines.AddRange(SplitParagraph(paragraphs[i],
columns, offsetCol, i));
if (outputLines.Count > lines) {
while (outputLines.Count > lines) {
outputLines.RemoveAt(outputLines.Count - 1);
}
break;
}
}
}
else {
for (int i = paragraphs.Count - 1; i >= 0; i--) {
outputLines.InsertRange(
0, SplitParagraph(paragraphs[i], columns, 0, i));
if (outputLines.Count > lines) {
while (outputLines.Count > lines) {
outputLines.RemoveAt(0);
}
break;
}
}
}
return outputLines;
}
///
/// Splits a paragraph into separate lines with a given number of
/// columns.
///
/// the paragraph string
/// the number of columns per line
///
/// the offset position within the paragraph
///
/// the paragraph's index
/// a list of lines
static List SplitParagraph(
AttributedText paragraph, int columns, int offset,
int paragraphIndex)
{
List lines = new List();
if (offset > paragraph.GetLength()) {
return lines;
}
char[] lineChars = new char[columns];
AttributedText line = new AttributedText(lineChars);
uint[] lineAttr = line.Attributes;
char[] text = paragraph.Text;
uint[] attr = paragraph.Attributes;
int textLength = text.Length;
int textEnd = textLength - 1;
int textPos = offset;
int lineStart = offset;
int linePos = 0;
int wordStart = 0;
while (true) {
if ((textPos > 0 && textPos < textEnd
&& text[textPos] == ' '
&& text[textPos + 1] != ' ')
|| (textPos == textEnd && linePos < columns)) {
wordStart = textPos + 1;
}
if (linePos == columns || textPos == textLength) {
//
// Cut line
//
if (wordStart > lineStart) {
while (textPos > wordStart) {
lineChars[--linePos] = '\0';
lineAttr[linePos] = 0;
textPos--;
}
}
//
// Ignore blank at the end of a line
//
if (textPos < textEnd && text[textPos] == ' ') {
textPos++;
}
//
// Add line
//
line.Origin.Column = lineStart;
line.Origin.Paragraph = paragraphIndex;
lines.Add(line);
lineChars = new char[columns];
line = new AttributedText(lineChars);
lineAttr = line.Attributes;
linePos = 0;
lineStart = textPos;
//
// End of paragraph
//
if (textPos == textLength) {
break;
}
}
else {
if (attr[textPos] != 0) {
line.AddAttribute(attr[textPos], linePos);
}
lineChars[linePos++] = text[textPos++];
}
}
return lines;
}
///
/// Extracts the substring from 'text' that's located between the given
/// strings 'start' and 'end'.
///
/// the text
/// the start string
/// the end string
///
public static string Substring(string text, string start, string end)
{
if (text == null || (text != null && text.Length == 0)
|| start == null || (start != null && start.Length == 0)
|| end == null || (end != null && end.Length == 0)) {
return null;
}
string result = null;
int startIndex = -1;
if ((startIndex = text.IndexOf(start)) > -1) {
startIndex += start.Length;
int length = text.IndexOf(end, startIndex) - startIndex;
if (length > 0) {
result = text.Substring(startIndex, length);
}
}
return result;
}
}
///
/// Represents a text position defined via paragraph and column.
///
public class TextPosition : IComparable
{
// -- Attributes -----------------------------------------------------
///
/// The text position's paragraph.
///
int paragraph = 0;
///
/// The text position's column.
///
int column = 0;
// -- Constructors ---------------------------------------------------
///
/// Initializes a new instance of the TextPosition class.
///
public TextPosition() { }
///
/// Initializes a new instance of the TextPosition class.
///
/// a paragraph
/// a column
public TextPosition(int paragraph, int column)
{
this.column = column;
this.paragraph = paragraph;
}
// -- Methods --------------------------------------------------------
///
/// Copies the paragraph and column values of a given text position.
///
/// a text position
public void Copy(TextPosition pos)
{
if (pos != null) {
column = pos.column;
paragraph = pos.paragraph;
}
}
///
/// Clears the paragraph and column values.
///
public void Clear()
{
column = paragraph = 0;
}
///
/// Determines whether the specified Object is equal to the current
/// Object.
///
///
/// the Object to compare with the current Object
///
///
/// true if the specified Object is equal to the current Object;
/// otherwise, false
///
public override bool Equals(Object obj)
{
if (obj == null) return false;
if (this.GetType() != obj.GetType()) return false;
TextPosition pos = (TextPosition)obj;
return (paragraph == pos.Paragraph && column == pos.Column);
}
///
/// Serves as a hash function for a particular type.
///
/// a hash code for the current Object
public override int GetHashCode()
{
return base.GetHashCode();
}
///
/// Compares the current instance with another object of the same type
/// and returns an integer that indicates whether the current instance
/// precedes, follows, or occurs in the same position in the sort order
/// as the other object.
///
/// an object to compare with this instance
///
/// a value that indicates the relative order of the objects being
/// compared
///
public int CompareTo(Object obj)
{
TextPosition pos = (TextPosition)obj;
if (paragraph > pos.Paragraph) {
return 1;
}
if (paragraph < pos.Paragraph) {
return -1;
}
if (column > pos.Column) {
return 1;
}
if (column < pos.Column) {
return -1;
}
return 0;
}
///
/// Returns a String that represents the current Object.
///
/// a String that represents the current Object
public override string ToString()
{
return "TextPosition { Paragraph=" + paragraph + ", Column="
+ column + " }";
}
// -- Accessors ------------------------------------------------------
///
/// Gets or sets the text position's column.
///
public int Column
{
get {
return column;
}
set {
column = value;
}
}
///
/// Gets or sets the text position's paragraph.
///
public int Paragraph
{
get {
return paragraph;
}
set {
paragraph = value;
}
}
}
///
/// Represents text with attribute information.
///
public class AttributedText
{
// -- Constants ------------------------------------------------------
///
/// The text attribute that indicates the start of user input.
///
public const uint ATTR_INPUT_START = 0x00000001;
///
/// The text attribute that indicates the end of user input.
///
public const uint ATTR_INPUT_END = 0x00000002;
///
/// The text attribute that indicates the caret position.
///
public const uint ATTR_INPUT_CARET = 0x00000004;
// -- Attributes -----------------------------------------------------
///
/// The text associated with this instance.
///
char[] text = null;
///
/// An array with text attributes. The indices correspond to the
/// indices of the array.
///
uint[] attributes = null;
///
/// The text position this attributed text came from.
///
TextPosition origin = new TextPosition();
// -- Constructor ----------------------------------------------------
///
/// Initializes a new instance of the AttributedText class.
///
/// the text associated with this instance
public AttributedText(string text)
{
this.text = text.ToCharArray();
this.attributes = new uint[text.Length];
}
///
/// Initializes a new instance of the AttributedText class.
///
/// the text associated with this instance
public AttributedText(char[] text)
{
this.text = text;
this.attributes = new uint[text.Length];
}
// -- Methods --------------------------------------------------------
///
/// Appends a string to the attributed text.
///
/// the string to append
public void Append(string append)
{
Append(append.ToCharArray());
}
///
/// Appends a character array to the attributed text.
///
/// the character array to append
public void Append(char[] append)
{
int oldSize = text.Length;
int resize = oldSize + append.Length;
Array.Resize(ref text, resize);
Array.Resize(ref attributes, resize);
Array.Copy(append, 0, text, oldSize, resize - oldSize);
}
///
/// Inserts a string in the attributed text.
///
/// the string to insert
/// the insert position
public void Insert(string insert, int pos)
{
Insert(insert.ToCharArray(), pos);
}
///
/// Inserts a character array in the attributed text.
///
/// the character array to insert
/// the insert position
public void Insert(char[] insert, int pos)
{
if (insert.Length == 0 || pos < 0 || pos >= text.Length) {
return;
}
int insSize = insert.Length;
int oldSize = text.Length;
int newSize = oldSize + insSize;
Array.Resize(ref text, newSize);
Array.Resize(ref attributes, newSize);
Array.Copy(text, pos, text, pos + insSize, oldSize - pos);
Array.Copy(insert, 0, text, pos, insSize);
}
///
/// Removes a range of the attributed text.
///
/// the start position
/// the end position
public void RemoveRange(int start, int end)
{
if (start < 0 || start > end || end >= text.Length) {
return;
}
int oldSize = text.Length;
int moveSize = oldSize - end - 1;
if (end < text.Length - 1) {
Array.Copy(text, end + 1, text, start, moveSize);
}
Array.Resize(ref text, start + moveSize);
Array.Resize(ref attributes, start + moveSize);
}
///
/// Provides a range of the attributed text.
///
/// the start position
/// the end position
///
/// a range of the attributed text or null if an error occurred
///
public AttributedText GetRange(int start, int end)
{
if (start < 0 || start > end || end >= text.Length) {
return null;
}
char[] range = new char[end - start + 1];
Array.Copy(text, start, range, 0, end - start + 1);
return new AttributedText(range);
}
///
/// Removes blanks at the attributed text's end.
///
public void TrimEnd()
{
int cut = text.Length;
while (--cut >= 0 && text[cut] == ' ');
Array.Resize(ref text, cut + 1);
Array.Resize(ref attributes, cut + 1);
}
///
/// Adds an attribute at the given position.
///
/// a text attribute
/// the text position
public void AddAttribute(uint attribute, int pos)
{
if (attributes == null || pos < 0 || pos >= attributes.Length) {
return;
}
attributes[pos] = attributes[pos] | attribute;
}
///
/// Removes an attribute at the given position.
///
/// a text attribute
/// the text position
public void RemoveAttribute(uint attribute, int pos)
{
if (attributes == null || pos < 0 || pos >= attributes.Length) {
return;
}
attributes[pos] = attributes[pos] & (~attribute);
}
///
/// Provides the text's length.
///
/// the text's lenght
public int GetLength()
{
return this.text.Length;
}
///
/// Determines whether the specified Object is equal to the current
/// Object.
///
///
/// the Object to compare with the current Object
///
///
/// true if the specified Object is equal to the current Object;
/// otherwise, false
///
public override bool Equals(Object obj)
{
if (obj == null) return false;
if (this.GetType() != obj.GetType()) return false;
bool equal = true;
AttributedText compare = (AttributedText)obj;
if (text.Length == compare.GetLength()) {
char[] compareChars = compare.Text;
uint[] compareAttr = compare.Attributes;
for (int i = 0; i < text.Length; i++) {
if (text[i] != compareChars[i]
|| attributes[i] != compareAttr[i]) {
equal = false;
break;
}
}
}
else {
equal = false;
}
return equal;
}
///
/// Serves as a hash function for a particular type.
///
/// a hash code for the current Object
public override int GetHashCode()
{
return base.GetHashCode();
}
///
/// Returns a String that represents the current Object.
///
/// a String that represents the current Object
public override string ToString()
{
return new string(text);
}
// -- Accessors ------------------------------------------------------
///
/// Gets the text attributes.
///
public uint[] Attributes
{
get {
return attributes;
}
}
///
/// Gets the text associated with this instance.
///
public char[] Text
{
get {
return text;
}
}
///
/// Gets the text position this attributed text came from.
///
public TextPosition Origin
{
get {
return origin;
}
}
}
}