/* * 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; } } } }