// Copyright (c) 2001, Dr Martin Porter // Copyright (c) 2002, Richard Boulton // Copyright (c) 2015, Cesar Souza // Copyright (c) 2018, Olly Betts // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // * this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // * notice, this list of conditions and the following disclaimer in the // * documentation and/or other materials provided with the distribution. // * Neither the name of the copyright holders nor the names of its contributors // * may be used to endorse or promote products derived from this software // * without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Snowball { using System; using System.Linq; using System.Text; /// /// Class holding current state. /// /// public class Env { /// /// Initializes a new instance of the class. /// /// protected Env() { } /// /// Gets the current string. /// /// protected StringBuilder current; /// /// Current cursor position. /// /// protected int cursor; /// /// Forward limit for inspecting the buffer. /// /// protected int limit; /// /// Backward limit for inspecting the buffer. /// /// protected int limit_backward; /// /// Starting bracket position. /// /// protected int bra; /// /// Ending bracket position. /// /// protected int ket; /// /// Copy another Env object. /// /// public Env(Env other) { copy_from(other); } /// /// Copy another Env object. /// /// protected void copy_from(Env other) { current = other.current; cursor = other.cursor; limit = other.limit; limit_backward = other.limit_backward; bra = other.bra; ket = other.ket; } } /// /// Base class for Snowball's stemmer algorithms. /// /// public abstract class Stemmer : Env { /// /// Initializes a new instance of the class. /// /// protected Stemmer() { current = new StringBuilder(); setBufferContents(""); } /// /// Calls the stemmer to process the next word. /// /// protected abstract bool stem(); /// /// Stems the buffer's contents. /// /// public bool Stem() { return this.stem(); } /// /// Stems a given word. /// /// /// The word to be stemmed. /// /// The stemmed word. /// public string Stem(string word) { setBufferContents(word); this.stem(); return current.ToString(); } /// /// Gets the current processing buffer. /// /// public StringBuilder Buffer { get { return current; } } /// /// Gets or sets the current word to be stemmed /// or the stemmed word, if the stemmer has been /// processed. /// /// public string Current { get { return current.ToString(); } set { setBufferContents(value); } } private void setBufferContents(string value) { current.Clear(); current.Insert(0, value); cursor = 0; limit = current.Length; limit_backward = 0; bra = cursor; ket = limit; } /// /// Determines whether the current character is /// inside a given group of characters s. /// protected int in_grouping(string s, int min, int max, bool repeat) { do { if (cursor >= limit) return -1; char ch = current[cursor]; if (ch > max || ch < min) return 1; if (!s.Contains(ch)) return 1; cursor++; } while (repeat); return 0; } /// /// Determines whether the current character is /// inside a given group of characters s. /// protected int in_grouping_b(string s, int min, int max, bool repeat) { do { if (cursor <= limit_backward) return -1; char ch = current[cursor - 1]; if (ch > max || ch < min) return 1; if (!s.Contains(ch)) return 1; cursor--; } while (repeat); return 0; } /// /// Determines whether the current character is /// outside a given group of characters s. /// protected int out_grouping(string s, int min, int max, bool repeat) { do { if (cursor >= limit) return -1; char ch = current[cursor]; if (ch > max || ch < min) { cursor++; continue; } if (!s.Contains(ch)) { cursor++; continue; } return 1; } while (repeat); return 0; } /// /// Determines whether the current character is /// outside a given group of characters s. /// protected int out_grouping_b(string s, int min, int max, bool repeat) { do { if (cursor <= limit_backward) return -1; char ch = current[cursor - 1]; if (ch > max || ch < min) { cursor--; continue; } if (!s.Contains(ch)) { cursor--; continue; } return 1; } while (repeat); return 0; } /// /// Determines if the current buffer contains the /// string s, starting from the current position and /// going forward. /// protected bool eq_s(String s) { if (limit - cursor < s.Length) return false; for (int i = 0; i != s.Length; i++) { if (current[cursor + i] != s[i]) return false; } cursor += s.Length; return true; } /// /// Determines if the current buffer contains the /// string s, starting from the current position and /// going backwards. /// protected bool eq_s_b(String s) { if (cursor - limit_backward < s.Length) return false; for (int i = 0; i != s.Length; i++) { if (current[cursor - s.Length + i] != s[i]) return false; } cursor -= s.Length; return true; } /// /// Determines if the current buffer contains the /// string s, starting from the current position and /// going backwards. /// protected bool eq_s_b(StringBuilder s) { if (cursor - limit_backward < s.Length) return false; for (int i = 0; i != s.Length; i++) { if (current[cursor - s.Length + i] != s[i]) return false; } cursor -= s.Length; return true; } /// /// Searches if the current buffer matches against one of the /// amongs, starting from the current cursor position and going /// forward. /// /// protected int find_among(Among[] v) { int i = 0; int j = v.Length; int c = cursor; int l = limit; int common_i = 0; int common_j = 0; bool first_key_inspected = false; while (true) { int k = i + ((j - i) >> 1); int diff = 0; int common = common_i < common_j ? common_i : common_j; // smaller Among w = v[k]; for (int i2 = common; i2 < w.SearchString.Length; i2++) { if (c + common == l) { diff = -1; break; } diff = current[c + common] - w.SearchString[i2]; if (diff != 0) break; common++; } if (diff < 0) { j = k; common_j = common; } else { i = k; common_i = common; } if (j - i <= 1) { if (i > 0) break; // v->s has been inspected if (j == i) break; // only one item in v // - but now we need to go round once more to get // v->s inspected. This looks messy, but is actually // the optimal approach. if (first_key_inspected) break; first_key_inspected = true; } } while (true) { Among w = v[i]; if (common_i >= w.SearchString.Length) { cursor = c + w.SearchString.Length; if (w.Action == null) return w.Result; bool res = w.Action(); cursor = c + w.SearchString.Length; if (res) return w.Result; } i = w.MatchIndex; if (i < 0) return 0; } } /// /// Searches if the current buffer matches against one of the /// amongs, starting from the current cursor position and going /// backwards. /// /// protected int find_among_b(Among[] v) { int i = 0; int j = v.Length; int c = cursor; int lb = limit_backward; int common_i = 0; int common_j = 0; bool first_key_inspected = false; while (true) { int k = i + ((j - i) >> 1); int diff = 0; int common = common_i < common_j ? common_i : common_j; Among w = v[k]; for (int i2 = w.SearchString.Length - 1 - common; i2 >= 0; i2--) { if (c - common == lb) { diff = -1; break; } diff = current[c - 1 - common] - w.SearchString[i2]; if (diff != 0) break; common++; } if (diff < 0) { j = k; common_j = common; } else { i = k; common_i = common; } if (j - i <= 1) { if (i > 0) break; if (j == i) break; if (first_key_inspected) break; first_key_inspected = true; } } while (true) { Among w = v[i]; if (common_i >= w.SearchString.Length) { cursor = c - w.SearchString.Length; if (w.Action == null) return w.Result; bool res = w.Action(); cursor = c - w.SearchString.Length; if (res) return w.Result; } i = w.MatchIndex; if (i < 0) return 0; } } /// /// Replaces the characters between c_bra /// and c_ket by the characters in s. /// /// protected int replace_s(int c_bra, int c_ket, String s) { int adjustment = s.Length - (c_ket - c_bra); Replace(current, c_bra, c_ket, s); limit += adjustment; if (cursor >= c_ket) cursor += adjustment; else if (cursor > c_bra) cursor = c_bra; return adjustment; } /// /// Checks if a slicing can be done. /// protected void slice_check() { if (bra < 0 || bra > ket || ket > limit || limit > current.Length) { System.Diagnostics.Trace.WriteLine("faulty slice operation"); } } /// /// Replaces the contents of the bracket with the string s. /// /// /// The s. protected void slice_from(String s) { slice_check(); replace_s(bra, ket, s); } /// /// Removes the current bracket contents. /// /// protected void slice_del() { slice_from(""); } /// /// Replaces the contents of the bracket with the string s. /// /// protected void insert(int c_bra, int c_ket, String s) { int adjustment = replace_s(c_bra, c_ket, s); if (c_bra <= bra) bra += adjustment; if (c_bra <= ket) ket += adjustment; } /// /// Replaces the contents of the bracket with the string s. /// /// protected void insert(int c_bra, int c_ket, StringBuilder s) { int adjustment = replace_s(c_bra, c_ket, s.ToString()); if (c_bra <= bra) bra += adjustment; if (c_bra <= ket) ket += adjustment; } /// /// Replaces the contents of the bracket with the string s. /// /// protected void slice_to(StringBuilder s) { slice_check(); Replace(s, 0, s.Length, current.ToString(bra, ket - bra)); } /// /// Replaces the contents of the bracket with the string s. /// /// protected void assign_to(StringBuilder s) { Replace(s, 0, s.Length, current.ToString(0, limit)); } /// /// Replaces a specific region of the buffer with another text. /// public static StringBuilder Replace(StringBuilder sb, int index, int length, string text) { sb.Remove(index, length - index); sb.Insert(index, text); return sb; } } }