refactoring of SVG tokenizing

This commit is contained in:
hneemann 2018-12-04 21:48:31 +01:00
parent 53c15330d7
commit 0e09c9fb28
6 changed files with 539 additions and 397 deletions

View File

@ -9,19 +9,13 @@ package de.neemann.digital.draw.graphics;
* Creates a polygon from a path
*/
public class PolygonParser {
enum Token {EOF, COMMAND, NUMBER}
private final String path;
private int lastTokenPos;
private int pos;
private char command;
private float value;
private final SVGTokenizer t;
private float x;
private float y;
private VectorFloat polyStart;
private VectorInterface lastQuadraticControlPoint;
private VectorInterface lastCubicControlPoint;
private String command = "";
/**
* Creates a new instance
@ -29,84 +23,26 @@ public class PolygonParser {
* @param path the path to parse
*/
public PolygonParser(String path) {
this.path = path;
pos = 0;
t = new SVGTokenizer(path);
}
Token next() {
lastTokenPos = pos;
while (pos < path.length() && (path.charAt(pos) == ' ' || path.charAt(pos) == ','))
pos++;
if (pos == path.length())
return Token.EOF;
char c = path.charAt(pos);
if (Character.isAlphabetic(c)) {
pos++;
command = c;
return Token.COMMAND;
} else {
value = parseNumber();
return Token.NUMBER;
}
private float nextValue() throws SVGTokenizer.TokenizerException {
return t.readFloat();
}
private char peekChar() {
return path.charAt(pos);
}
private float parseNumber() {
int p0 = pos;
if (peekChar() == '+' || peekChar() == '-')
pos++;
while (pos < path.length() && (Character.isDigit(peekChar()) || peekChar() == '.'))
pos++;
if (pos < path.length() && (peekChar() == 'e' || peekChar() == 'E')) {
pos++;
if (peekChar() == '+' || peekChar() == '-')
pos++;
while (pos < path.length() && (Character.isDigit(peekChar()) || peekChar() == '.'))
pos++;
}
return Float.parseFloat(path.substring(p0, pos));
}
private void unreadToken() {
pos = lastTokenPos;
}
char getCommand() {
return command;
}
double getValue() {
return value;
}
private float nextValue() throws ParserException {
if (next() != Token.NUMBER)
throw new ParserException("expected a number at pos " + pos + " in '" + path + "'");
return value;
}
private VectorFloat nextVector() throws ParserException {
private VectorFloat nextVector() throws SVGTokenizer.TokenizerException {
x = nextValue();
y = nextValue();
return new VectorFloat(x, y);
}
private VectorFloat nextVectorInc() throws ParserException {
private VectorFloat nextVectorInc() throws SVGTokenizer.TokenizerException {
x += nextValue();
y += nextValue();
return new VectorFloat(x, y);
}
private VectorFloat nextVectorRel() throws ParserException {
private VectorFloat nextVectorRel() throws SVGTokenizer.TokenizerException {
return new VectorFloat(x + nextValue(), y + nextValue());
}
@ -117,110 +53,113 @@ public class PolygonParser {
* @throws ParserException ParserException
*/
public Polygon create() throws ParserException {
Polygon p = new Polygon(false);
Token tok;
boolean closedPending = false;
while ((tok = next()) != Token.EOF) {
if (tok == Token.NUMBER) {
unreadToken();
if (command == 'm')
command = 'l';
else if (command == 'M')
command = 'L';
}
switch (command) {
case 'M':
if (closedPending) {
closedPending = false;
p.addClosePath();
}
p.addMoveTo(setPolyStart(nextVector()));
clearControl();
break;
case 'm':
if (closedPending) {
closedPending = false;
p.addClosePath();
}
p.addMoveTo(setPolyStart(nextVectorInc()));
clearControl();
break;
case 'V':
y = nextValue();
p.add(getCurrent());
clearControl();
break;
case 'v':
y += nextValue();
p.add(getCurrent());
clearControl();
break;
case 'H':
x = nextValue();
p.add(getCurrent());
clearControl();
break;
case 'h':
x += nextValue();
p.add(getCurrent());
clearControl();
break;
case 'l':
p.add(nextVectorInc());
clearControl();
break;
case 'L':
p.add(nextVector());
clearControl();
break;
case 'c':
p.add(nextVectorRel(), setLastC3(nextVectorRel()), nextVectorInc());
break;
case 'C':
p.add(nextVector(), setLastC3(nextVector()), nextVector());
break;
case 'q':
p.add(setLastC2(nextVectorRel()), nextVectorInc());
break;
case 'Q':
p.add(setLastC2(nextVector()), nextVector());
break;
case 's':
addCubicWithReflect(p, getCurrent(), nextVectorRel(), nextVectorInc());
break;
case 'S':
addCubicWithReflect(p, getCurrent(), nextVector(), nextVector());
break;
case 't':
addQuadraticWithReflect(p, getCurrent(), nextVectorInc());
break;
case 'T':
addQuadraticWithReflect(p, getCurrent(), nextVector());
break;
case 'a':
addArc(p, getCurrent(), nextValue(), nextValue(), nextValue(), nextValue() != 0, nextValue() != 0, nextVectorInc());
clearControl();
break;
case 'A':
addArc(p, getCurrent(), nextValue(), nextValue(), nextValue(), nextValue() != 0, nextValue() != 0, nextVector());
clearControl();
break;
case 'Z':
case 'z':
closedPending = true;
if (polyStart != null) {
x = polyStart.getXFloat();
y = polyStart.getYFloat();
}
clearControl();
break;
default:
throw new ParserException("unsupported path command " + command);
try {
Polygon p = new Polygon(false);
boolean closedPending = false;
while (!t.isEOF()) {
if (t.nextIsNumber()) {
if (command.equals("m"))
command = "l";
else if (command.equals("M"))
command = "L";
} else
command = t.readCommand();
switch (command) {
case "M":
if (closedPending) {
closedPending = false;
p.addClosePath();
}
p.addMoveTo(setPolyStart(nextVector()));
clearControl();
break;
case "m":
if (closedPending) {
closedPending = false;
p.addClosePath();
}
p.addMoveTo(setPolyStart(nextVectorInc()));
clearControl();
break;
case "V":
y = nextValue();
p.add(getCurrent());
clearControl();
break;
case "v":
y += nextValue();
p.add(getCurrent());
clearControl();
break;
case "H":
x = nextValue();
p.add(getCurrent());
clearControl();
break;
case "h":
x += nextValue();
p.add(getCurrent());
clearControl();
break;
case "l":
p.add(nextVectorInc());
clearControl();
break;
case "L":
p.add(nextVector());
clearControl();
break;
case "c":
p.add(nextVectorRel(), setLastC3(nextVectorRel()), nextVectorInc());
break;
case "C":
p.add(nextVector(), setLastC3(nextVector()), nextVector());
break;
case "q":
p.add(setLastC2(nextVectorRel()), nextVectorInc());
break;
case "Q":
p.add(setLastC2(nextVector()), nextVector());
break;
case "s":
addCubicWithReflect(p, getCurrent(), nextVectorRel(), nextVectorInc());
break;
case "S":
addCubicWithReflect(p, getCurrent(), nextVector(), nextVector());
break;
case "t":
addQuadraticWithReflect(p, getCurrent(), nextVectorInc());
break;
case "T":
addQuadraticWithReflect(p, getCurrent(), nextVector());
break;
case "a":
addArc(p, getCurrent(), nextValue(), nextValue(), nextValue(), nextValue() != 0, nextValue() != 0, nextVectorInc());
clearControl();
break;
case "A":
addArc(p, getCurrent(), nextValue(), nextValue(), nextValue(), nextValue() != 0, nextValue() != 0, nextVector());
clearControl();
break;
case "Z":
case "z":
closedPending = true;
if (polyStart != null) {
x = polyStart.getXFloat();
y = polyStart.getYFloat();
}
clearControl();
break;
default:
throw new ParserException("unsupported path command " + command);
}
}
if (closedPending)
p.setClosed(true);
return p;
} catch (SVGTokenizer.TokenizerException e) {
throw new ParserException("error parsing a path", e);
}
if (closedPending)
p.setClosed(true);
return p;
}
private VectorFloat setPolyStart(VectorFloat v) {
@ -408,6 +347,10 @@ public class PolygonParser {
private ParserException(String message) {
super(message);
}
private ParserException(String message, Exception cause) {
super(message, cause);
}
}
/**
@ -417,7 +360,11 @@ public class PolygonParser {
* @throws ParserException ParserException
*/
public Polygon parsePolygon() throws ParserException {
return parsePolygonPolyline(true);
try {
return parsePolygonPolyline(true);
} catch (SVGTokenizer.TokenizerException e) {
throw new ParserException("error parsing a polygon", e);
}
}
/**
@ -427,13 +374,17 @@ public class PolygonParser {
* @throws ParserException ParserException
*/
public Polygon parsePolyline() throws ParserException {
return parsePolygonPolyline(false);
try {
return parsePolygonPolyline(false);
} catch (SVGTokenizer.TokenizerException e) {
throw new ParserException("error parsing a polyline", e);
}
}
private Polygon parsePolygonPolyline(boolean closed) throws ParserException {
private Polygon parsePolygonPolyline(boolean closed) throws SVGTokenizer.TokenizerException {
Polygon p = new Polygon(closed);
while (next() != Token.EOF)
p.add(new VectorFloat(value, nextValue()));
while (!t.isEOF())
p.add(new VectorFloat(nextValue(), nextValue()));
return p;
}

View File

@ -0,0 +1,250 @@
/*
* Copyright (c) 2018 Helmut Neemann.
* Use of this source code is governed by the GPL v3 license
* that can be found in the LICENSE file.
*/
package de.neemann.digital.draw.graphics;
/**
* A tokenizer to parse svg strings
*/
public class SVGTokenizer {
/**
* token types
*/
public enum Token {
/**
* EOF
*/
EOF,
/**
* a command
*/
COMMAND,
/**
* a number
*/
NUMBER,
/**
* a character
*/
CHAR
}
private final String code;
private int lastTokenPos;
private int pos;
private StringBuilder command;
private float value;
private char character;
/**
* Creates a new instance
*
* @param code the code to parse
*/
public SVGTokenizer(String code) {
this.code = code;
command = new StringBuilder();
pos = 0;
}
private Token next() throws TokenizerException {
lastTokenPos = pos;
while (pos < code.length() && (Character.isWhitespace(code.charAt(pos)) || code.charAt(pos) == ','))
pos++;
if (pos == code.length())
return Token.EOF;
character = code.charAt(pos);
if (Character.isAlphabetic(character)) {
command.setLength(0);
pos++;
command.append(character);
while (pos < code.length() && Character.isAlphabetic(code.charAt(pos))) {
command.append(code.charAt(pos));
pos++;
}
return Token.COMMAND;
}
if (Character.isDigit(character) || character == '-' || character == '+') {
value = parseNumber();
return Token.NUMBER;
} else {
pos++;
return Token.CHAR;
}
}
private char peekChar() {
return code.charAt(pos);
}
private float parseNumber() throws TokenizerException {
int p0 = pos;
if (peekChar() == '+' || peekChar() == '-')
pos++;
while (pos < code.length() && (Character.isDigit(peekChar()) || peekChar() == '.'))
pos++;
if (pos < code.length() && (peekChar() == 'e' || peekChar() == 'E')) {
pos++;
if (peekChar() == '+' || peekChar() == '-')
pos++;
while (pos < code.length() && (Character.isDigit(peekChar()) || peekChar() == '.'))
pos++;
}
try {
return Float.parseFloat(code.substring(p0, pos));
} catch (NumberFormatException e) {
throw new TokenizerException("not a number " + code.substring(p0, pos));
}
}
private void unreadToken() {
pos = lastTokenPos;
}
/**
* Expect the given character c
*
* @param c the expected character
* @throws TokenizerException TokenizerException
*/
public void expect(char c) throws TokenizerException {
if (next() != Token.CHAR)
throw new TokenizerException("expected character " + c);
if (character != c)
throw new RuntimeException("expected " + c + " found " + character);
}
/**
* Reads a float
*
* @return the float
* @throws TokenizerException TokenizerException
*/
public float readFloat() throws TokenizerException {
if (next() != Token.NUMBER)
throw new TokenizerException("expected a number");
return value;
}
/**
* Reads a command
*
* @return the command
* @throws TokenizerException TokenizerException
*/
public String readCommand() throws TokenizerException {
if (next() != Token.COMMAND)
throw new TokenizerException("expected a command");
return command.toString();
}
/**
* @return true if the next token is a number
* @throws TokenizerException TokenizerException
*/
public boolean nextIsNumber() throws TokenizerException {
if (next() == Token.NUMBER) {
unreadToken();
return true;
} else {
unreadToken();
return false;
}
}
/**
* Checks if the next char is the given char.
*
* @param c the char
* @return true if next char is the given char.
* @throws TokenizerException TokenizerException
*/
public boolean nextIsChar(char c) throws TokenizerException {
if (next() == Token.CHAR) {
if (character == c) {
return true;
} else {
unreadToken();
return false;
}
} else {
unreadToken();
return false;
}
}
/**
* @return true if the EOF is reached
* @throws TokenizerException TokenizerException
*/
public boolean isEOF() throws TokenizerException {
if (next() == Token.EOF) {
return true;
} else {
unreadToken();
return false;
}
}
@Override
public String toString() {
return code + " (" + pos + ")";
}
/**
* @return the remainig string
*/
public String remaining() {
final String s = code.substring(pos).trim();
pos = code.length();
return s;
}
/**
* Reads all up to the given character
*
* @param c the character
* @return the data string
*/
public String readTo(char c) {
int p = pos;
int brace = 0;
while (pos < code.length() && (code.charAt(pos) != c || brace != 0)) {
switch (code.charAt(pos)) {
case '(':
brace++;
break;
case ')':
brace--;
break;
}
pos++;
}
final String r = code.substring(p, pos).trim();
while (pos < code.length() && code.charAt(pos) == c)
pos++;
return r;
}
/**
* Exception thrown by the tokenizer
*/
public final class TokenizerException extends Exception {
private TokenizerException(String message) {
super(message);
}
}
}

View File

@ -5,10 +5,7 @@
*/
package de.neemann.digital.draw.shapes.custom.svg;
import de.neemann.digital.draw.graphics.Orientation;
import de.neemann.digital.draw.graphics.Transform;
import de.neemann.digital.draw.graphics.VectorFloat;
import de.neemann.digital.draw.graphics.VectorInterface;
import de.neemann.digital.draw.graphics.*;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
@ -21,7 +18,6 @@ class Context {
private static final HashMap<String, AttrParser> PARSER = new HashMap<>();
static {
PARSER.put("transform", (c1, value1) -> c1.tr = Transform.mul(new TransformParser(value1).parse(), c1.tr));
PARSER.put("fill", (c, value) -> c.fill = getColorFromString(value));
@ -82,16 +78,18 @@ class Context {
}
static Context readStyle(Context context, String style) throws SvgException {
StringTokenizer st = new StringTokenizer(style, ";");
while (st.hasMoreTokens()) {
String[] t = st.nextToken().split(":");
if (t.length == 2) {
AttrParser p = PARSER.get(t[0].trim());
SVGTokenizer t = new SVGTokenizer(style);
try {
while (!t.isEOF()) {
String command = t.readTo(':');
AttrParser p = PARSER.get(command);
if (p != null)
p.parse(context, t[1].trim());
p.parse(context, t.readTo(';'));
}
return context;
} catch (SVGTokenizer.TokenizerException e) {
throw new SvgException("invalid svg style: " + style, e);
}
return context;
}
Transform getTransform() {
@ -155,16 +153,15 @@ class Context {
}
void addClasses(String classes) {
classes = classes.trim();
while (classes.startsWith(".")) {
int p1 = classes.indexOf("{");
int p2 = classes.indexOf("}");
if (p1 < 0 || p2 < 0)
return;
String key = classes.substring(1, p1);
String val = classes.substring(p1 + 1, p2);
classesMap.put(key, val);
classes = classes.substring(p2 + 1).trim();
SVGTokenizer t = new SVGTokenizer(classes);
try {
while (t.nextIsChar('.')) {
String key=t.readTo('{');
String val = t.readTo('}');
classesMap.put(key, val);
}
} catch (SVGTokenizer.TokenizerException e) {
// ignore errors
}
}
@ -187,31 +184,39 @@ class Context {
}
private static Color getColorFromString(String v) {
if (v.equalsIgnoreCase("none"))
return null;
try {
if (v.startsWith("#")) {
if (v.length() == 4)
return new Color(sRGB(v.charAt(1)), sRGB(v.charAt(2)), sRGB(v.charAt(3)));
SVGTokenizer t = new SVGTokenizer(v);
if (t.nextIsChar('#')) {
String c = t.remaining();
if (c.length() == 3)
return new Color(sRGB(c.charAt(0)), sRGB(c.charAt(1)), sRGB(c.charAt(2)));
else
return Color.decode(v);
} else if (v.startsWith("rgb(")) {
StringTokenizer st = new StringTokenizer(v.substring(4), " ,)");
return new Color(rgb(st.nextToken()), rgb(st.nextToken()), rgb(st.nextToken()));
} else {
final String command = t.readCommand();
switch (command) {
case "none":
return null;
case "rgb":
t.expect('(');
Color col = new Color(rgb(t), rgb(t), rgb(t));
t.expect(')');
return col;
default:
return (Color) Color.class.getField(command.toLowerCase()).get(null);
}
}
return (Color) Color.class.getField(v.toLowerCase()).get(null);
} catch (RuntimeException | IllegalAccessException | NoSuchFieldException e) {
} catch (Exception e) {
return Color.BLACK;
}
}
private static int rgb(String s) {
if (s.endsWith("%"))
return (int) (Float.parseFloat(s.substring(0, s.length() - 1)) / 100 * 255);
private static int rgb(SVGTokenizer t) throws SVGTokenizer.TokenizerException {
float v = t.readFloat();
if (t.nextIsChar('%'))
return (int) (v * 2.55f);
else
return Integer.parseInt(s);
return (int) v;
}
private static int sRGB(char c) {
@ -220,10 +225,11 @@ class Context {
}
private static float getFloatFromString(String inp) {
inp = inp.replaceAll("[^0-9.]", "");
if (inp.isEmpty())
try {
return Float.parseFloat(inp);
} catch (NumberFormatException e) {
return 1;
return Float.parseFloat(inp);
}
}
}

View File

@ -5,24 +5,14 @@
*/
package de.neemann.digital.draw.shapes.custom.svg;
import de.neemann.digital.draw.graphics.Transform;
import de.neemann.digital.draw.graphics.TransformMatrix;
import de.neemann.digital.draw.graphics.TransformTranslate;
import de.neemann.digital.draw.graphics.VectorFloat;
import de.neemann.digital.draw.graphics.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class TransformParser {
private static final Logger LOGGER = LoggerFactory.getLogger(TransformParser.class);
enum Token {EOF, COMMAND, NUMBER, CHAR}
private final String transform;
private int lastTokenPos;
private int pos;
private StringBuilder command;
private float value;
private char character;
private SVGTokenizer tok;
/**
* Creates a new instance
@ -30,94 +20,7 @@ class TransformParser {
* @param transform the path to parse
*/
TransformParser(String transform) {
this.transform = transform;
command = new StringBuilder();
pos = 0;
}
private Token next() {
lastTokenPos = pos;
while (pos < transform.length() && (transform.charAt(pos) == ' ' || transform.charAt(pos) == ','))
pos++;
if (pos == transform.length())
return Token.EOF;
character = transform.charAt(pos);
if (Character.isAlphabetic(character)) {
command.setLength(0);
pos++;
command.append(character);
while (pos < transform.length() && Character.isAlphabetic(transform.charAt(pos))) {
command.append(transform.charAt(pos));
pos++;
}
return Token.COMMAND;
}
if (Character.isDigit(character) || character == '-' || character == '+') {
value = parseNumber();
return Token.NUMBER;
} else {
pos++;
return Token.CHAR;
}
}
private char peekChar() {
return transform.charAt(pos);
}
private float parseNumber() {
int p0 = pos;
if (peekChar() == '+' || peekChar() == '-')
pos++;
while (pos < transform.length() && (Character.isDigit(peekChar()) || peekChar() == '.'))
pos++;
if (pos < transform.length() && (peekChar() == 'e' || peekChar() == 'E')) {
pos++;
if (peekChar() == '+' || peekChar() == '-')
pos++;
while (pos < transform.length() && (Character.isDigit(peekChar()) || peekChar() == '.'))
pos++;
}
return Float.parseFloat(transform.substring(p0, pos));
}
private void unreadToken() {
pos = lastTokenPos;
}
private String getCommand() {
return command.toString();
}
private float getValue() {
return value;
}
private void expect(char c) {
if (next() != Token.CHAR)
throw new RuntimeException("expected character " + c);
if (character != c)
throw new RuntimeException("expected " + c + " found " + character);
}
private float readFloat() {
if (next() != Token.NUMBER)
throw new RuntimeException("expected a number");
return value;
}
private float readOptionalFloat(float def) {
if (next() != Token.NUMBER) {
unreadToken();
return def;
} else
return value;
tok = new SVGTokenizer(transform);
}
/**
@ -129,36 +32,36 @@ class TransformParser {
Transform combined = Transform.IDENTITY;
try {
Transform t;
while (true) {
final Token tok = next();
if (tok == Token.EOF)
break;
if (tok != Token.COMMAND)
throw new RuntimeException("invalid transform", null);
switch (getCommand()) {
while (!tok.isEOF()) {
final String command = tok.readCommand();
switch (command) {
case "translate":
expect('(');
final float x = readFloat();
float y = readOptionalFloat(0);
expect(')');
tok.expect('(');
final float x = tok.readFloat();
float y = 0;
if (tok.nextIsNumber())
y = tok.readFloat();
tok.expect(')');
t = new TransformTranslate(new VectorFloat(x, y));
break;
case "scale":
expect('(');
final float xs = readFloat();
float ys = readOptionalFloat(xs);
expect(')');
tok.expect('(');
final float xs = tok.readFloat();
float ys = xs;
if (tok.nextIsNumber())
ys = tok.readFloat();
tok.expect(')');
t = new TransformMatrix(xs, 0, 0, ys, 0, 0);
break;
case "matrix":
expect('(');
final float ma = readFloat();
final float mb = readFloat();
final float mc = readFloat();
final float md = readFloat();
final float mx = readFloat();
final float my = readFloat();
expect(')');
tok.expect('(');
final float ma = tok.readFloat();
final float mb = tok.readFloat();
final float mc = tok.readFloat();
final float md = tok.readFloat();
final float mx = tok.readFloat();
final float my = tok.readFloat();
tok.expect(')');
t = new TransformMatrix(
ma,
mc,
@ -168,27 +71,25 @@ class TransformParser {
my);
break;
case "rotate":
expect('(');
float w = readFloat();
if (next() == Token.NUMBER) {
tok.expect('(');
float w = tok.readFloat();
if (tok.nextIsNumber()) {
t = TransformMatrix.rotate(w);
float xc = getValue();
float yc = readFloat();
float xc = tok.readFloat();
float yc = tok.readFloat();
t = Transform.mul(new TransformTranslate(-xc, -yc), t);
t = Transform.mul(t, new TransformTranslate(xc, yc));
} else {
unreadToken();
} else
t = TransformMatrix.rotate(w);
}
expect(')');
tok.expect(')');
break;
default:
throw new RuntimeException("unknown transform: " + value, null);
throw new RuntimeException("unknown transform: " + command, null);
}
combined = Transform.mul(t, combined);
}
} catch (RuntimeException e) {
LOGGER.warn(transform + ": " + e.getMessage());
} catch (Exception e) {
LOGGER.warn(tok + ": " + e.getMessage());
}
return combined;
}

View File

@ -1,42 +0,0 @@
/*
* Copyright (c) 2016 Helmut Neemann
* Use of this source code is governed by the GPL v3 license
* that can be found in the LICENSE file.
*/
package de.neemann.digital.draw.graphics;
import junit.framework.TestCase;
public class PolygonParserTest extends TestCase {
public void testSimple() {
PolygonParser pp = new PolygonParser("m 1,2");
assertEquals(PolygonParser.Token.COMMAND, pp.next());
assertEquals('m', pp.getCommand());
assertEquals(PolygonParser.Token.NUMBER, pp.next());
assertEquals(1.0, pp.getValue(), 1e-6);
assertEquals(PolygonParser.Token.NUMBER, pp.next());
assertEquals(2.0, pp.getValue(), 1e-6);
assertEquals(PolygonParser.Token.EOF, pp.next());
assertEquals(PolygonParser.Token.EOF, pp.next());
}
public void testSimpleExp() {
PolygonParser pp = new PolygonParser("1e1");
assertEquals(PolygonParser.Token.NUMBER, pp.next());
assertEquals(10, pp.getValue(), 1e-6);
assertEquals(PolygonParser.Token.EOF, pp.next());
}
public void testSimpleExpSign() {
PolygonParser pp = new PolygonParser("1e-1");
assertEquals(PolygonParser.Token.NUMBER, pp.next());
assertEquals(0.1, pp.getValue(), 1e-6);
assertEquals(PolygonParser.Token.EOF, pp.next());
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) 2018 Helmut Neemann.
* Use of this source code is governed by the GPL v3 license
* that can be found in the LICENSE file.
*/
package de.neemann.digital.draw.graphics;
import junit.framework.TestCase;
public class SVGTokenizerTest extends TestCase {
public void testSimple() throws SVGTokenizer.TokenizerException {
SVGTokenizer t = new SVGTokenizer("m 1,2");
assertEquals("m", t.readCommand());
assertEquals(1.0, t.readFloat(), 1e-6);
assertEquals(2.0, t.readFloat(), 1e-6);
assertTrue(t.isEOF());
assertTrue(t.isEOF());
}
public void testSimpleExp() throws SVGTokenizer.TokenizerException {
SVGTokenizer t = new SVGTokenizer("1e1");
assertEquals(10, t.readFloat(), 1e-6);
assertTrue(t.isEOF());
}
public void testSimpleExpSign() throws SVGTokenizer.TokenizerException {
SVGTokenizer t = new SVGTokenizer("1e-1");
assertEquals(0.1, t.readFloat(), 1e-6);
assertTrue(t.isEOF());
}
public void testRemaining() throws SVGTokenizer.TokenizerException {
SVGTokenizer t = new SVGTokenizer("test:World(-)");
assertEquals("test", t.readCommand());
t.expect(':');
assertEquals("World(-)", t.remaining());
assertTrue(t.isEOF());
}
public void testReadto() throws SVGTokenizer.TokenizerException {
SVGTokenizer t = new SVGTokenizer("test:World(-);");
assertEquals("test", t.readCommand());
t.expect(':');
assertEquals("World(-)", t.readTo(';'));
assertTrue(t.isEOF());
}
public void testReadtoEnd() throws SVGTokenizer.TokenizerException {
SVGTokenizer t = new SVGTokenizer("test:World(-)");
assertEquals("test", t.readCommand());
t.expect(':');
assertEquals("World(-)", t.readTo(';'));
assertTrue(t.isEOF());
}
public void testReadtoEndNested() throws SVGTokenizer.TokenizerException {
SVGTokenizer t = new SVGTokenizer("test:World(;)");
assertEquals("test", t.readCommand());
t.expect(':');
assertEquals("World(;)", t.readTo(';'));
assertTrue(t.isEOF());
}
public void testReadtoEndNested2() throws SVGTokenizer.TokenizerException {
SVGTokenizer t = new SVGTokenizer("test:World(;);test:World");
assertEquals("test", t.readCommand());
t.expect(':');
assertEquals("World(;)", t.readTo(';'));
assertEquals("test", t.readCommand());
t.expect(':');
assertEquals("World", t.readTo(';'));
assertTrue(t.isEOF());
}
}