433 lines
12 KiB
Java
433 lines
12 KiB
Java
/*
|
|
Nile2Unit.java
|
|
*
|
|
* Copyright (c) 2000, Ted Nelson and Tuomas Lukka
|
|
*
|
|
* You may use and distribute under the terms of either the GNU Lesser
|
|
* General Public License, either version 2 of the license or,
|
|
* at your choice, any later version. Alternatively, you may use and
|
|
* distribute under the terms of the XPL.
|
|
*
|
|
* See the LICENSE.lgpl and LICENSE.xpl files for the specific terms of
|
|
* the licenses.
|
|
*
|
|
* 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 README
|
|
* file for more details.
|
|
*
|
|
*/
|
|
/*
|
|
* Written by Tuomas Lukka
|
|
*/
|
|
|
|
package org.gzigzag.module;
|
|
import org.gzigzag.*;
|
|
|
|
/** A nile unit of division.
|
|
*/
|
|
|
|
public abstract class Nile2Unit {
|
|
public static final String rcsid = "$Id: Nile2Unit.java,v 1.5 2000/12/17 16:32:24 bfallenstein Exp $";
|
|
public static boolean dbg = false;
|
|
static final void p(String s) { if(dbg) System.out.println(s); }
|
|
static final void pa(String s) { System.out.println(s); }
|
|
|
|
|
|
/** The previous or next position where
|
|
* this type of unit may be inserted.
|
|
*/
|
|
public abstract boolean insertPos(Nile2Iter iter, int dir,
|
|
boolean includeThis);
|
|
|
|
/** The previous or next beginning position of this type of
|
|
* unit.
|
|
*/
|
|
public abstract boolean start(Nile2Iter iter, int dir, boolean includeThis);
|
|
|
|
/** The previous or next ending position of this type of unit.
|
|
*/
|
|
public abstract boolean end(Nile2Iter iter, int dir, boolean includeThis);
|
|
|
|
/** Cut the stream.
|
|
* This routine modifies the start and to iterators to point
|
|
* to the beginning and end of the modified stream.
|
|
* @return The pasted-together cutaway point, from which
|
|
* the insert position can be searched with !includeThis.
|
|
*
|
|
*/
|
|
public abstract Nile2Iter cut(Nile2Iter start, Nile2Iter to);
|
|
|
|
/** Copy a part of the stream.
|
|
* First every cell is spanified, then a new stream with the same spans
|
|
* is created.
|
|
* This routine modifies the start and to iterators to point
|
|
* to the beginning and end of the copied stream.
|
|
* XXX any special copying rules to obey, like in cut?
|
|
*/
|
|
public void copy(Nile2Iter start, Nile2Iter to) {
|
|
this.start(start, -1, true);
|
|
this.end(to, 1, true);
|
|
Nile2Iter.copy(start, to);
|
|
}
|
|
|
|
public abstract void paste(Nile2Iter at, Nile2Iter what, Nile2Iter whatEnd);
|
|
|
|
|
|
static public class Char extends Nile2Unit {
|
|
public boolean insertPos(Nile2Iter iter, int dir,
|
|
boolean includeThis) {
|
|
if(!includeThis)
|
|
if(!iter.move(dir))
|
|
if(!iter.shiftPara(dir))
|
|
return false;
|
|
return true;
|
|
}
|
|
public boolean start(Nile2Iter iter, int dir,
|
|
boolean includeThis) {
|
|
if(!includeThis)
|
|
if(!iter.move(dir))
|
|
if(!iter.shiftPara(dir))
|
|
return false;
|
|
if(iter.get() == -1) return false;
|
|
return true;
|
|
}
|
|
|
|
public boolean end(Nile2Iter iter, int dir,
|
|
boolean includeThis) {
|
|
if(!includeThis)
|
|
if(!iter.move(dir))
|
|
if(!iter.shiftPara(dir))
|
|
return false;
|
|
if(iter.get(-1) == -1) return false;
|
|
return true;
|
|
}
|
|
|
|
public Nile2Iter cut(Nile2Iter start, Nile2Iter to) {
|
|
Nile2Iter after = to.cut();
|
|
start.cut();
|
|
start.join(after);
|
|
return start;
|
|
}
|
|
|
|
public void paste(Nile2Iter at, Nile2Iter what, Nile2Iter whatEnd) {
|
|
}
|
|
}
|
|
|
|
|
|
/** The word and sentence base class.
|
|
* Words and sentences obey the same rules for the insert point.
|
|
*/
|
|
static public abstract class WordOrsentence extends Nile2Unit {
|
|
public abstract boolean isStart(Nile2Iter iter);
|
|
public abstract boolean isEnd(Nile2Iter iter);
|
|
public abstract int nspacesAtEnd();
|
|
|
|
// As documented,
|
|
// word insert points are word starting points, as well as
|
|
// paragraph's ending points.
|
|
public boolean insertPos(Nile2Iter iter, int dir,
|
|
boolean includeThis) {
|
|
return startOrIns(iter, dir, includeThis, true);
|
|
}
|
|
|
|
public boolean start(Nile2Iter iter, int dir, boolean includeThis) {
|
|
return startOrIns(iter, dir, includeThis, false);
|
|
}
|
|
|
|
public boolean startOrIns(Nile2Iter iter, int dir, boolean includeThis,
|
|
boolean insert) {
|
|
// This way we can always assume includeThis later.
|
|
if(!includeThis)
|
|
if(!iter.move(dir)) {
|
|
if(!iter.shiftPara(dir))
|
|
return false;
|
|
// If we were looking for an insert point, we just
|
|
// found it...
|
|
if(dir < 0 && insert) return true;
|
|
}
|
|
// Can optimize if necessary...
|
|
while(true) {
|
|
if(isStart(iter)) return true;
|
|
if(!iter.move(dir)) {
|
|
if(dir > 0 && insert) return true;
|
|
if(!iter.shiftPara(dir)) return false;
|
|
if(dir < 0 && insert) return true;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
public boolean end(Nile2Iter iter, int dir, boolean includeThis) {
|
|
if(!includeThis)
|
|
if(!iter.move(dir)) {
|
|
if(!iter.shiftPara(dir)) return false;
|
|
// In forwards dir, just go to the beginning
|
|
// of the next paragraph.
|
|
// This way, cutting from the last insertpos until
|
|
// end() results in just the paragraph break being
|
|
// removed, which is just right.
|
|
return true;
|
|
}
|
|
while(true) {
|
|
if(isEnd(iter)) return true;
|
|
if(!iter.move(dir))
|
|
if(!iter.shiftPara(dir))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public Nile2Iter cut(Nile2Iter start, Nile2Iter end) {
|
|
// Find first non-space character.
|
|
int nws = this.firstNonWs(start);
|
|
this.p("Cut Firstnonws: "+nws + " '"+(char)nws+"'");
|
|
boolean startPunct = this.punct(nws);
|
|
|
|
// cut all the space after end.
|
|
int ncut = 0;
|
|
Nile2Iter endclone = (Nile2Iter)end.clone();
|
|
while(this.ws(end.get())) { end.move(1); ncut ++; }
|
|
// If we happen to have space before us, count that too!
|
|
int nspace = 0;
|
|
while(this.ws(endclone.prev())) nspace++;
|
|
|
|
Nile2Iter rstart = null;
|
|
if(ncut + nspace == 0 || startPunct) {
|
|
// If no space at end,
|
|
// we must cut space from before this, if it
|
|
// is a single space.
|
|
if(this.ws(start.get(-1)) && !this.ws(start.get(-2)))
|
|
(rstart = (Nile2Iter)start.clone()). move(-1);
|
|
else
|
|
if(this.ws(start.get(-1)) && startPunct) {
|
|
(rstart = (Nile2Iter)start.clone()). move(-1);
|
|
// For the if down later
|
|
ncut = 1;
|
|
}
|
|
}
|
|
// Now, here's the cut. We take away the stuff from
|
|
// start to end and then remove from rstart
|
|
// We must cut starting from the end so as not to disturb
|
|
// things.
|
|
Nile2Iter after = end.cut();
|
|
Nile2Iter first = start.cut();
|
|
Nile2Iter before = (Nile2Iter)start.clone();
|
|
start.set(first); // We want to return this in start.
|
|
// Finally, cut the other stuff.
|
|
if(rstart != null) {
|
|
rstart.cut();
|
|
before = rstart;
|
|
}
|
|
before.join(after);
|
|
// This is a bit special rule.
|
|
if(startPunct && ncut + nspace > 0)
|
|
before.insertInCell(" ");
|
|
return before;
|
|
}
|
|
|
|
public void paste(Nile2Iter at, Nile2Iter what, Nile2Iter whatEnd) {
|
|
// Check whether what has punctuation in the start...
|
|
int nws = this.firstNonWs(what);
|
|
this.p("Paste Firstnonws: "+nws + " '"+(char)nws+"'");
|
|
boolean startPunct = this.punct(nws);
|
|
Nile2Iter after = null;
|
|
|
|
if(dbg) {
|
|
this.p("PASTE!!!");
|
|
at.dumpStream();
|
|
what.dumpStream();
|
|
}
|
|
|
|
|
|
if(startPunct) {
|
|
// If there is punctuation, we must start it with
|
|
// exactly the spaces that were there, UNLESS
|
|
// we are at the beginning of the sentence, in which
|
|
// case we shall have two spaces.
|
|
|
|
if(this.ws(at.get(-1)) && this.ws(at.get(-2))) {
|
|
// Ok, now it's difficult.
|
|
// Leave these spaces, but remove one in the
|
|
// beginning of
|
|
what.adjustSpaceStart(0);
|
|
after = at.cut();
|
|
} else {
|
|
if(this.ws(at.get(-1))) {
|
|
at.move(-1);
|
|
after = at.cut();
|
|
after.move(1);
|
|
after = after.cut();
|
|
} else {
|
|
after = at.cut();
|
|
}
|
|
}
|
|
} else {
|
|
// pasting words is a bit tricky.
|
|
int prev = at.get(-1);
|
|
int cur = at.get();
|
|
// Where do we want to ensure we have space.
|
|
boolean spaceEnd = false, spaceStart = false;
|
|
// If we are at start of paragraph, we ensure that
|
|
// a space is in the end.
|
|
if(prev == -1) spaceEnd = true;
|
|
else if(this.punct(cur)) {
|
|
// If we are at punctuation, we put a space after
|
|
// inserted text is there was a space before.
|
|
if(this.ws(prev)) spaceEnd = true;
|
|
else spaceStart = true;
|
|
}
|
|
else spaceEnd = true;
|
|
|
|
what.adjustSpaces(spaceStart ? 1 : 0,
|
|
spaceEnd ? nspacesAtEnd() : 0);
|
|
|
|
|
|
after = at.cut();
|
|
}
|
|
at.join(what);
|
|
whatEnd.set(what);
|
|
whatEnd.streamEnd(1);
|
|
whatEnd.join(after);
|
|
whatEnd.set(after);
|
|
}
|
|
}
|
|
|
|
static public class Word extends WordOrsentence {
|
|
public boolean isStart(Nile2Iter iter) { return this.isWordStart(iter); }
|
|
public boolean isEnd(Nile2Iter iter) { return this.isWordEnd(iter); }
|
|
public int nspacesAtEnd() { return 1; }
|
|
}
|
|
|
|
static public class Sentence extends WordOrsentence {
|
|
public boolean isStart(Nile2Iter iter) { return this.isSentenceStart(iter); }
|
|
public boolean isEnd(Nile2Iter iter) { return this.isSentenceEnd(iter); }
|
|
public int nspacesAtEnd() { return 2; }
|
|
}
|
|
|
|
static public class Paragraph extends Nile2Unit {
|
|
// A paragraph may be inserted at the end of any paragraph,
|
|
// or in the very beginning of the stream.
|
|
// That requires special handling because
|
|
// Nile2Iter can't get there.
|
|
public boolean insertPos(Nile2Iter iter, int dir,
|
|
boolean includeThis) {
|
|
// XXX First, check if we *are* there already.
|
|
|
|
if(!end(iter, dir, includeThis)) {
|
|
if(dir > 0) return false;
|
|
ZZCursor curs = iter.getCursor(1);
|
|
curs.set(curs.get().h("d.nile"));
|
|
curs.setOffs(0);
|
|
iter.set(curs);
|
|
}
|
|
return true;
|
|
}
|
|
public boolean start(Nile2Iter iter, int dir, boolean includeThis) {
|
|
if(!includeThis) {
|
|
if(!iter.move(dir)) {
|
|
// We were at the beginning
|
|
if(!iter.shiftPara(dir))
|
|
return false;
|
|
}
|
|
}
|
|
return iter.paraEnd(-1);
|
|
}
|
|
public boolean end(Nile2Iter iter, int dir, boolean includeThis) {
|
|
if(!includeThis) {
|
|
if(!iter.move(dir)) {
|
|
// We were at the beginning
|
|
if(!iter.shiftPara(dir))
|
|
return false;
|
|
}
|
|
}
|
|
return iter.paraEnd(1);
|
|
}
|
|
|
|
public Nile2Iter cut(Nile2Iter start, Nile2Iter end) {
|
|
return null;
|
|
}
|
|
public void paste(Nile2Iter at, Nile2Iter what, Nile2Iter whatEnd) {
|
|
}
|
|
}
|
|
|
|
// Something non-ws following to ws's.
|
|
static public boolean isSentenceStart(Nile2Iter iter) {
|
|
int cur = iter.get();
|
|
if(cur == -1 || ws(cur)) return false;
|
|
int prev = iter.get(-1);
|
|
if(prev == -1) return true;
|
|
if(!ws(prev)) return false;
|
|
int prev2 = iter.get(-2);
|
|
if(prev2 == -1) return true;
|
|
if(!ws(prev2)) return false;
|
|
return true;
|
|
}
|
|
|
|
// Something non-ws following to ws's.
|
|
static public boolean isSentenceEnd(Nile2Iter iter) {
|
|
int prev = iter.get(-1);
|
|
if(prev == -1 || ws(prev)) return false;
|
|
int cur = iter.get();
|
|
if(cur == -1) return true;
|
|
if(!ws(cur)) return false;
|
|
int next = iter.get(1);
|
|
if(next == -1) return true;
|
|
if(!ws(next)) return false;
|
|
return true;
|
|
}
|
|
|
|
static public boolean isWordStart(Nile2Iter iter) {
|
|
int cur = iter.get();
|
|
if(punct(cur)) {
|
|
/* if(lett(punct.get(-1)) && lett(punct.get(1))) {
|
|
// Punctuation surrounded by two letters is NOT
|
|
// a word boundary.
|
|
return false;
|
|
} else XXX Must test in other cases!!! */
|
|
|
|
return true;
|
|
}
|
|
|
|
int prev = iter.get(-1);
|
|
// This should also cover buffer start.
|
|
if(lett(cur) && !lett(prev)) return true;
|
|
return false;
|
|
}
|
|
static public boolean isWordEnd(Nile2Iter iter) {
|
|
int prev = iter.get(-1);
|
|
if(punct(prev))
|
|
/*
|
|
if(lett(punct.get(-2)) && lett(punct.get(0))) {
|
|
// Punctuation surrounded by two letters is NOT
|
|
// a word boundary.
|
|
return false;
|
|
} else XXX Must test!!!! */
|
|
return true;
|
|
|
|
int cur = iter.get();
|
|
if(lett(prev) && !lett(cur)) return true;
|
|
return false;
|
|
}
|
|
|
|
// Shorthand.
|
|
// Exactly one of these must return true for every character.
|
|
// except -1, when all are false.
|
|
static boolean ws(int c) { return Character.isWhitespace((char)c); }
|
|
static boolean punct(int c) {
|
|
if(c == -1) return false;
|
|
return (!Character.isWhitespace((char)c)) &&
|
|
(!Character.isLetterOrDigit((char)c));
|
|
}
|
|
static boolean lett(int c) { return Character.isLetterOrDigit((char)c); }
|
|
|
|
static int firstNonWs(Nile2Iter iter) {
|
|
int ch = 0;
|
|
// Use index, don't move.
|
|
for(int i=0; (ch=iter.get(i)) != -1; i++) {
|
|
if(!ws(ch)) return ch;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
}
|