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