Rev 3294 |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/*
* Copyright (C) 2014.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 or
* version 2 as published by the Free Software Foundation.
*
* This program 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.
*/
package uk.me.parabola.util;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.NavigableSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.ibm.icu.text.CollationElementIterator;
import com.ibm.icu.text.Collator;
import com.ibm.icu.text.RuleBasedCollator;
//import java.text.CollationElementIterator;
//import java.text.Collator;
//import java.text.RuleBasedCollator;
/**
* Create a set of rules for a given code page.
*
* Should be usable, perhaps with a few tweaks.
* Works with unicode too, need to choose which blocks to take for unicode.
*
* @author Steve Ratcliffe
*/
public class CollationRules
{
private CharsetDecoder decoder
;
private final NavigableSet
<CharPosition
> positionMap =
new TreeSet<>();
private final NavigableSet
<CharPosition
> basePositionMap =
new TreeSet<>();
private final Map<Character, CharPosition
> charMap =
new HashMap<>();
private boolean isUnicode
;
private Charset charset
;
public static void main
(String[] args
) {
String charsetName = args
[0];
CollationRules main =
new CollationRules
();
main.
go(charsetName
);
}
private void go
(String charsetName
) {
RuleBasedCollator col =
(RuleBasedCollator) Collator.
getInstance();
charset =
Charset.
forName(charsetName
);
if (charsetName.
equalsIgnoreCase("utf-8"))
isUnicode =
true;
decoder = charset.
newDecoder();
if (isUnicode
)
addUnicode
();
else
addBlock
(col,
0);
printCharMap
();
printExpansions
();
}
private void addBlock
(RuleBasedCollator col,
int block
) {
for (int i =
0; i
< 0x100
; i++
) {
int ch =
(block
<< 8) + i
;
String testString = getString
(ch
);
char conv = testString.
charAt(0);
if (Character.
getType(conv
) ==
Character.
UNASSIGNED || conv == 0xfffd
)
continue;
CollationElementIterator it = col.
getCollationElementIterator(testString
);
System.
out.
printf("# %s ", fmtChar
(testString.
charAt(0)));
int next
;
int index =
0;
CharPosition cp =
new CharPosition
(0);
while ((next = it.
next()) !=
CollationElementIterator.
NULLORDER) {
if (index ==
0) {
cp =
new CharPosition
(ch
);
cp.
setOrder(next
);
} else {
assert index
< 3;
if ((next
& 0xffff0000
) ==
0) {
cp.
addOrder(next, index
);
} else {
cp.
addChar(new CharPosition
(ch
));
cp.
setOrder(next
);
}
}
index++
;
}
System.
out.
printf(" %s %d", cp,
Character.
getType(cp.
getUnicode()));
System.
out.
println();
tweak
(cp
);
if (ch
> 0)
positionMap.
add(cp
);
if (cp.
nextChar ==
null) {
basePositionMap.
add(cp
);
charMap.
put(conv, cp
);
}
}
}
private void addUnicode
() {
Pattern pat =
Pattern.
compile("([0-9A-F]{4,5}) ? ; \\[[.*](.*)\\] #.*");
try (FileReader r =
new FileReader("allkeys.txt")) {
try (BufferedReader br =
new BufferedReader(r
)) {
String line
;
while ((line = br.
readLine()) !=
null) {
Matcher matcher = pat.
matcher(line
);
if (matcher.
matches()) {
String weights = matcher.
group(2);
int ch =
Integer.
parseInt(matcher.
group(1),
16);
if (ch
> 0xffff
)
continue;
System.
out.
printf("# %04x %s ", ch, fmtChar
(ch
));
String[] split = weights.
split("]\\[[.*]");
int index =
0;
CharPosition cp =
new CharPosition
(0);
for (String s : split
) {
String[] ws = s.
split("\\.");
int next =
Integer.
parseInt(ws
[0],
16) << 16
|
((Integer.
parseInt(ws
[1],
16) << 8) & 0xff00
)
|
((Integer.
parseInt(ws
[2],
16)) & 0xff
);
if (index ==
0) {
cp =
new CharPosition
(ch
);
cp.
setOrder(next
);
} else {
if ((next
& 0xffff0000
) ==
0) {
cp.
addOrder(next, index
);
} else {
cp.
addChar(new CharPosition
(ch
));
cp.
setOrder(next
);
}
}
index++
;
}
System.
out.
printf(" %s %d\n", cp,
Character.
getType(cp.
getUnicode()));
tweak
(cp
);
if (ch
> 0)
positionMap.
add(cp
);
if (cp.
nextChar ==
null) {
basePositionMap.
add(cp
);
charMap.
put((char) ch, cp
);
}
} else {
System.
out.
println("# NOMATCH: " + line
);
}
}
}
} catch (IOException e
) {
e.
printStackTrace();
}
}
/**
* Fix up a few characters that we always want to be in well known places.
*
* @param cp The position to change.
*/
private void tweak
(CharPosition cp
) {
if (cp.
val < 8)
cp.
third = cp.
val +
7;
if (!isUnicode
) {
switch (cp.
getUnicode()) {
case '¼':
cp.
nextChar = charMap.
get('/').
copy();
cp.
nextChar.
nextChar = charMap.
get('4');
break;
case '½':
cp.
nextChar = charMap.
get('/').
copy();
cp.
nextChar.
nextChar = charMap.
get('2');
break;
case '¾':
cp.
nextChar = charMap.
get('/').
copy();
cp.
nextChar.
nextChar = charMap.
get('4');
break;
}
}
switch (cp.
getUnicode()) {
case '˜':
CharPosition tilde = charMap.
get('~');
cp.
first = tilde.
first;
cp.
second = tilde.
second +
1;
cp.
third = tilde.
third +
1;
cp.
nextChar =
null;
break;
}
}
private String getString
(int i
) {
if (isUnicode
)
return new String(new char[]{(char) i
});
else {
byte[] b =
{(byte) i
};
return new String(b,
0,
1, charset
);
}
}
private void printCharMap
() {
Formatter chars =
new Formatter();
chars.
format("\n");
CharPosition last =
new CharPosition
(0);
last.
first =
0;
for (CharPosition cp : positionMap
) {
if (cp.
isExpansion())
continue;
if (cp.
first != last.
first) {
chars.
format("\n < ");
} else if (cp.
second != last.
second) {
chars.
format(" ; ");
} else if (cp.
third != last.
third) {
chars.
format(",");
} else {
chars.
format("=");
}
last = cp
;
int uni = toUnicode
(cp.
val);
chars.
format("%s", fmtChar
(uni
));
}
System.
out.
println(chars
);
}
private void printExpansions
() {
for (CharPosition cp : positionMap
) {
if (!cp.
isExpansion())
continue;
Formatter fmt =
new Formatter();
//noinspection MalformedFormatString
fmt.
format("expand %c to", cp.
getUnicode());
boolean ok =
true;
for (CharPosition cp2 = cp
; cp2
!=
null; cp2 = cp2.
nextChar) {
cp2.
second = 0x50000
;
int top =
(cp2.
third >> 16) & 0xff
;
cp2.
third =
(top == 0x9e || top == 0xa2 || top == 0x2b
) ? 0x9b0000 :
0;
CharPosition floor = basePositionMap.
ceiling(cp2
);
if (floor ==
null || floor.
getUnicode() == 0xfffd
) {
fmt.
format(" NF");
ok =
false;
} else {
fmt.
format(" %s", fmtChar
(floor.
getUnicode()));
}
}
System.
out.
println((ok
? "" :
"# ") + fmt.
toString());
// Print comments to help find problems.
for (CharPosition cp2 = cp
; cp2
!=
null; cp2 = cp2.
nextChar) {
CharPosition floor = basePositionMap.
ceiling(cp2
);
if (floor ==
null) {
System.
out.
println("#FIX: NF ref=" + cp2
);
} else {
System.
out.
println("#floor is " + fmtChar
(toUnicode
(floor.
val)) +
", " + floor +
", ref is " + cp2
);
}
}
}
}
private String fmtChar
(int val
) {
boolean asChar =
true;
switch (val
) {
case '<':
case ';':
case ',':
case '=':
case '#':
asChar =
false;
break;
default:
switch (Character.
getType(val
)) {
case Character.
UNASSIGNED:
case Character.
NON_SPACING_MARK:
case Character.
FORMAT:
case Character.
CONTROL:
case Character.
SPACE_SEPARATOR:
case Character.
LINE_SEPARATOR:
case Character.
PARAGRAPH_SEPARATOR:
asChar =
false;
}
}
if (asChar
) {
//noinspection MalformedFormatString
return String.
format("%c", val
);
} else {
return String.
format("%04x", val
);
}
}
private int toUnicode
(int c
) {
if (isUnicode
)
return c
;
ByteBuffer b =
ByteBuffer.
allocate(1);
b.
put((byte) c
);
b.
flip();
try {
CharBuffer chars = decoder.
decode(b
);
return chars.
charAt(0);
} catch (CharacterCodingException e
) {
return '?';
}
}
class CharPosition
implements Comparable<CharPosition
> {
private final int val
;
private int first
;
private int second
;
private int third
;
private CharPosition nextChar
;
public CharPosition
(int charValue
) {
this.
val = charValue
;
}
public int compareTo
(CharPosition other
) {
if (other.
first == first
)
return compareSecond
(other
);
else if (first
< other.
first)
return -
1;
else
return 1;
}
private int compareSecond
(CharPosition c2
) {
if (c2.
second == second
)
return compareThird
(c2
);
else if (second
< c2.
second)
return -
1;
else
return 1;
}
private int compareThird
(CharPosition c2
) {
if (third == c2.
third)
return new Integer(val
).
compareTo(c2.
val);
else if (third
< c2.
third)
return -
1;
else
return 1;
}
public String toString
() {
Formatter fmt =
new Formatter();
toString
(fmt
);
return fmt.
toString();
}
private void toString
(Formatter fmt
) {
fmt.
format("[%04x %02x %02x]", first, second, third
);
if (nextChar
!=
null)
nextChar.
toString(fmt
);
}
public void setOrder
(int next
) {
if (nextChar
!=
null) {
nextChar.
setOrder(next
);
return;
}
first =
(next
>> 16) & 0xffff
;
second =
(next
<< 8) & 0xff0000
;
third =
(next
<< 16) & 0xff0000
;
}
public void addOrder
(int next,
int count
) {
assert ((next
>>> 16) & 0xffff
) ==
0;
if (this.
nextChar !=
null) {
this.
nextChar.
addOrder(next, count
);
return;
}
second +=
((next
>> 8) & 0xff
) << (2-count
)*8;
third +=
((next
) & 0xff
) << (2-count
)*8;
}
public boolean isExpansion
() {
return nextChar
!=
null;
}
public void addChar
(CharPosition pos
) {
if (nextChar
!=
null) {
nextChar.
addChar(pos
);
return;
}
nextChar = pos
;
}
public int getUnicode
() {
return toUnicode
(val
);
}
public CharPosition copy
() {
CharPosition cp =
new CharPosition
(this.
val);
cp.
first =
this.
first;
cp.
second =
this.
second;
cp.
third =
this.
third;
return cp
;
}
}
}