/*
* Copyright 2000-2013 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.whattf.datatype;
import java.io.StringReader;
import java.io.IOException;
import org.relaxng.datatype.DatatypeException;
public class SvgPathData extends AbstractDatatype {
/**
* Package-private constructor
*/
protected SvgPathData() {
super();
}
private static StringReader reader;
private static StringBuilder context;
private static final int MAX_CONTEXT_LENGTH = 20;
private void appendToContext(int i) {
if (i != -1) {
if (context.length() == MAX_CONTEXT_LENGTH) {
context.deleteCharAt(0);
}
context.append((char) i);
}
}
/**
* The current character.
*/
private int current;
@Override public void checkValid(CharSequence literal)
throws DatatypeException {
reader = new StringReader(literal.toString());
context = new StringBuilder(MAX_CONTEXT_LENGTH);
try {
current = reader.read();
appendToContext(current);
} catch (IOException e) {
throw new RuntimeException(e);
}
loop: for (;;) {
try {
switch (current) {
case 0xD:
case 0xA:
case 0x20:
case 0x9:
current = reader.read();
appendToContext(current);
break;
case 'z':
case 'Z':
current = reader.read();
appendToContext(current);
break;
case 'm':
checkm();
break;
case 'M':
checkM();
break;
case 'l':
checkl();
break;
case 'L':
checkL();
break;
case 'h':
checkh();
break;
case 'H':
checkH();
break;
case 'v':
checkv();
break;
case 'V':
checkV();
break;
case 'c':
checkc();
break;
case 'C':
checkC();
break;
case 'q':
checkq();
break;
case 'Q':
checkQ();
break;
case 's':
checks();
break;
case 'S':
checkS();
break;
case 't':
checkt();
break;
case 'T':
checkT();
break;
case 'a':
checka();
break;
case 'A':
checkA();
break;
case -1:
break loop;
default:
throw newDatatypeException("Expected command but "
+ "found \u201c" + (char) current
+ "\u201d (context: \u201c"
+ context.toString() + "\u201d).");
}
} catch (IOException e) {
try {
skipSubPath();
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
throw new RuntimeException(e);
}
}
try {
skipSpaces();
if (current != -1) {
throw newDatatypeException("Found unexpected character "
+ "\u201c" + (char) current + "\u201d.");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Checks an 'm' command.
*/
private void checkm() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
checkArg('m', "x coordinate");
skipCommaSpaces();
checkArg('m', "y coordinate");
boolean expectNumber = skipCommaSpaces2();
_checkl('m', expectNumber);
}
/**
* Checks an 'M' command.
*/
private void checkM() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
checkArg('M', "x coordinate");
skipCommaSpaces();
checkArg('M', "y coordinate");
boolean expectNumber = skipCommaSpaces2();
_checkL('M', expectNumber);
}
/**
* Checks an 'l' command.
*/
private void checkl() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
_checkl('l', true);
}
private void _checkl(char command, boolean expectNumber)
throws IOException, DatatypeException {
for (;;) {
switch (current) {
default:
if (expectNumber) {
reportUnexpected("coordinate pair for " + "\u201c"
+ command + "\u201d command", current);
skipSubPath();
}
return;
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
checkArg(command, "x coordinate");
skipCommaSpaces();
checkArg(command, "y coordinate");
expectNumber = skipCommaSpaces2();
}
}
/**
* Checks an 'L' command.
*/
private void checkL() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
_checkL('L', true);
}
private void _checkL(char command, boolean expectNumber)
throws IOException, DatatypeException {
for (;;) {
switch (current) {
default:
if (expectNumber) {
reportUnexpected("coordinate pair for " + "\u201c"
+ command + "\u201d command", current);
skipSubPath();
}
return;
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
checkArg(command, "x coordinate");
skipCommaSpaces();
checkArg(command, "y coordinate");
expectNumber = skipCommaSpaces2();
}
}
/**
* Checks an 'h' command.
*/
private void checkh() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
boolean expectNumber = true;
for (;;) {
switch (current) {
default:
if (expectNumber)
reportNonNumber('h', current);
return;
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
checkArg('h', "x coordinate");
expectNumber = skipCommaSpaces2();
}
}
/**
* Checks an 'H' command.
*/
private void checkH() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
boolean expectNumber = true;
for (;;) {
switch (current) {
default:
if (expectNumber)
reportNonNumber('H', current);
return;
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
checkArg('H', "x coordinate");
expectNumber = skipCommaSpaces2();
}
}
/**
* Checks a 'v' command.
*/
private void checkv() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
boolean expectNumber = true;
for (;;) {
switch (current) {
default:
if (expectNumber)
reportNonNumber('v', current);
return;
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
checkArg('v', "y coordinate");
expectNumber = skipCommaSpaces2();
}
}
/**
* Checks a 'V' command.
*/
private void checkV() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
boolean expectNumber = true;
for (;;) {
switch (current) {
default:
if (expectNumber)
reportNonNumber('V', current);
return;
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
checkArg('V', "y coordinate");
expectNumber = skipCommaSpaces2();
}
}
/**
* Checks a 'c' command.
*/
private void checkc() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
boolean expectNumber = true;
for (;;) {
switch (current) {
default:
if (expectNumber)
reportNonNumber('c', current);
return;
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
checkArg('c', "x1 coordinate");
skipCommaSpaces();
checkArg('c', "y1 coordinate");
skipCommaSpaces();
checkArg('c', "x2 coordinate");
skipCommaSpaces();
checkArg('c', "y2 coordinate");
skipCommaSpaces();
checkArg('c', "x coordinate");
skipCommaSpaces();
checkArg('c', "y coordinate");
expectNumber = skipCommaSpaces2();
}
}
/**
* Checks a 'C' command.
*/
private void checkC() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
boolean expectNumber = true;
for (;;) {
switch (current) {
default:
if (expectNumber)
reportNonNumber('C', current);
return;
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
checkArg('C', "x1 coordinate");
skipCommaSpaces();
checkArg('C', "y1 coordinate");
skipCommaSpaces();
checkArg('C', "x2 coordinate");
skipCommaSpaces();
checkArg('C', "y2 coordinate");
skipCommaSpaces();
checkArg('C', "x coordinate");
skipCommaSpaces();
checkArg('C', "y coordinate");
expectNumber = skipCommaSpaces2();
}
}
/**
* Checks a 'q' command.
*/
private void checkq() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
boolean expectNumber = true;
for (;;) {
switch (current) {
default:
if (expectNumber)
reportNonNumber('q', current);
return;
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
checkArg('q', "x1 coordinate");
skipCommaSpaces();
checkArg('q', "y1 coordinate");
skipCommaSpaces();
checkArg('q', "x coordinate");
skipCommaSpaces();
checkArg('q', "y coordinate");
expectNumber = skipCommaSpaces2();
}
}
/**
* Checks a 'Q' command.
*/
private void checkQ() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
boolean expectNumber = true;
for (;;) {
switch (current) {
default:
if (expectNumber)
reportNonNumber('Q', current);
return;
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
checkArg('Q', "x1 coordinate");
skipCommaSpaces();
checkArg('Q', "y1 coordinate");
skipCommaSpaces();
checkArg('Q', "x coordinate");
skipCommaSpaces();
checkArg('Q', "y coordinate");
expectNumber = skipCommaSpaces2();
}
}
/**
* Checks an 's' command.
*/
private void checks() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
boolean expectNumber = true;
for (;;) {
switch (current) {
default:
if (expectNumber)
reportNonNumber('s', current);
return;
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
checkArg('s', "x2 coordinate");
skipCommaSpaces();
checkArg('s', "y2 coordinate");
skipCommaSpaces();
checkArg('s', "x coordinate");
skipCommaSpaces();
checkArg('s', "y coordinate");
expectNumber = skipCommaSpaces2();
}
}
/**
* Checks an 'S' command.
*/
private void checkS() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
boolean expectNumber = true;
for (;;) {
switch (current) {
default:
if (expectNumber)
reportNonNumber('S', current);
return;
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
checkArg('S', "x2 coordinate");
skipCommaSpaces();
checkArg('S', "y2 coordinate");
skipCommaSpaces();
checkArg('S', "x coordinate");
skipCommaSpaces();
checkArg('S', "y coordinate");
expectNumber = skipCommaSpaces2();
}
}
/**
* Checks a 't' command.
*/
private void checkt() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
boolean expectNumber = true;
for (;;) {
switch (current) {
default:
if (expectNumber)
reportNonNumber('t', current);
return;
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
checkArg('t', "x coordinate");
skipCommaSpaces();
checkArg('t', "y coordinate");
expectNumber = skipCommaSpaces2();
}
}
/**
* Checks a 'T' command.
*/
private void checkT() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
boolean expectNumber = true;
for (;;) {
switch (current) {
default:
if (expectNumber)
reportNonNumber('T', current);
return;
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
checkArg('T', "x coordinate");
skipCommaSpaces();
checkArg('T', "y coordinate");
expectNumber = skipCommaSpaces2();
}
}
/**
* Checks an 'a' command.
*/
private void checka() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
boolean expectNumber = true;
for (;;) {
switch (current) {
default:
if (expectNumber)
reportNonNumber('a', current);
return;
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
checkArg('a', "rx radius");
skipCommaSpaces();
checkArg('a', "ry radius");
skipCommaSpaces();
checkArg('a', "x-axis-rotation");
skipCommaSpaces();
switch (current) {
default:
reportUnexpected("\u201c0\u201d or \u201c1\u201d "
+ "for large-arc-flag for \u201ca\u201d command",
current);
skipSubPath();
return;
case '0':
case '1':
break;
}
current = reader.read();
appendToContext(current);
skipCommaSpaces();
switch (current) {
default:
reportUnexpected("\u201c0\u201d or \u201c1\u201d "
+ "for sweep-flag for \u201ca\u201d command",
current);
skipSubPath();
return;
case '0':
case '1':
break;
}
current = reader.read();
appendToContext(current);
skipCommaSpaces();
checkArg('a', "x coordinate");
skipCommaSpaces();
checkArg('a', "y coordinate");
expectNumber = skipCommaSpaces2();
}
}
/**
* Checks an 'A' command.
*/
private void checkA() throws DatatypeException, IOException {
if (context.length() == 0) {
appendToContext(current);
}
current = reader.read();
appendToContext(current);
skipSpaces();
boolean expectNumber = true;
for (;;) {
switch (current) {
default:
if (expectNumber)
reportNonNumber('A', current);
return;
case '+':
case '-':
case '.':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break;
}
checkArg('A', "rx radius");
skipCommaSpaces();
checkArg('A', "ry radius");
skipCommaSpaces();
checkArg('A', "x-axis-rotation");
skipCommaSpaces();
switch (current) {
default:
reportUnexpected("\u201c0\u201d or \u201c1\u201d "
+ "for large-arc-flag for \u201cA\u201d command",
current);
skipSubPath();
return;
case '0':
case '1':
break;
}
current = reader.read();
appendToContext(current);
skipCommaSpaces();
switch (current) {
default:
reportUnexpected("\u201c0\u201d or \u201c1\u201d "
+ "for sweep-flag for \u201cA\u201d command",
current);
skipSubPath();
return;
case '0':
case '1':
break;
}
current = reader.read();
appendToContext(current);
skipCommaSpaces();
checkArg('A', "x coordinate");
skipCommaSpaces();
checkArg('A', "y coordinate");
expectNumber = skipCommaSpaces2();
}
}
/**
* Checks a command argument.
*/
private void checkArg(char command, String arg) throws DatatypeException,
IOException {
int mant = 0;
int mantDig = 0;
boolean mantPos = true;
boolean mantRead = false;
int exp = 0;
int expDig = 0;
int expAdj = 0;
boolean expPos = true;
switch (current) {
case '-':
mantPos = false;
case '+':
current = reader.read();
appendToContext(current);
}
m1: switch (current) {
default:
reportUnexpected(arg + " for \u201c" + command
+ "\u201d command", current);
skipSubPath();
return;
case '.':
break;
case '0':
mantRead = true;
l: for (;;) {
current = reader.read();
appendToContext(current);
switch (current) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break l;
case '.':
case 'e':
case 'E':
break m1;
default:
return;
case '0':
}
}
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
mantRead = true;
l: for (;;) {
if (mantDig < 9) {
mantDig++;
mant = mant * 10 + (current - '0');
} else {
expAdj++;
}
current = reader.read();
appendToContext(current);
switch (current) {
default:
break l;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
}
}
}
if (current == '.') {
current = reader.read();
appendToContext(current);
m2: switch (current) {
default:
case 'e':
case 'E':
if (!mantRead) {
reportNonNumber(command, current);
return;
}
break;
case '0':
if (mantDig == 0) {
l: for (;;) {
current = reader.read();
appendToContext(current);
expAdj--;
switch (current) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break l;
default:
if (!mantRead) {
return;
}
break m2;
case '0':
}
}
}
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
l: for (;;) {
if (mantDig < 9) {
mantDig++;
mant = mant * 10 + (current - '0');
expAdj--;
}
current = reader.read();
appendToContext(current);
switch (current) {
default:
break l;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
}
}
}
}
switch (current) {
case 'e':
case 'E':
current = reader.read();
appendToContext(current);
switch (current) {
default:
reportNonNumber(command, current);
return;
case '-':
expPos = false;
case '+':
current = reader.read();
appendToContext(current);
switch (current) {
default:
reportNonNumber(command, current);
return;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
}
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
}
en: switch (current) {
case '0':
l: for (;;) {
current = reader.read();
appendToContext(current);
switch (current) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
break l;
default:
break en;
case '0':
}
}
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
l: for (;;) {
if (expDig < 3) {
expDig++;
exp = exp * 10 + (current - '0');
}
current = reader.read();
appendToContext(current);
switch (current) {
default:
break l;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
}
}
}
default:
}
if (!expPos) {
exp = -exp;
}
exp += expAdj;
if (!mantPos) {
mant = -mant;
}
return;
}
/**
* Skips a sub-path.
*/
private void skipSubPath() throws IOException {
for (;;) {
switch (current) {
case -1:
case 'm':
case 'M':
return;
default:
break;
}
current = reader.read();
appendToContext(current);
}
}
/**
* Skips the whitespaces in the current reader.
*/
private void skipSpaces() throws IOException {
for (;;) {
switch (current) {
default:
return;
case 0x20:
case 0x09:
case 0x0D:
case 0x0A:
}
current = reader.read();
appendToContext(current);
}
}
/**
* Skips the whitespaces and an optional comma.
*/
private void skipCommaSpaces() throws IOException {
wsp1: for (;;) {
switch (current) {
default:
break wsp1;
case 0x20:
case 0x9:
case 0xD:
case 0xA:
}
current = reader.read();
appendToContext(current);
}
if (current == ',') {
wsp2: for (;;) {
switch (current = reader.read()) {
default:
appendToContext(current);
break wsp2;
case 0x20:
case 0x9:
case 0xA:
case 0xD:
appendToContext(current);
}
}
}
}
/**
* Skips the whitespaces and an optional comma.
*
* @returns true if comma was skipped.
*/
private boolean skipCommaSpaces2() throws IOException {
wsp1: for (;;) {
switch (current) {
default:
break wsp1;
case 0x20:
case 0x9:
case 0xD:
case 0xA:
break;
}
current = reader.read();
appendToContext(current);
}
if (current != ',')
return false; // no comma.
wsp2: for (;;) {
switch (current = reader.read()) {
default:
appendToContext(current);
break wsp2;
case 0x20:
case 0x9:
case 0xD:
case 0xA:
appendToContext(current);
break;
}
}
return true; // had comma
}
private void reportUnexpected(String expected, int ch)
throws DatatypeException, IOException {
if (ch != -1) {
throw newDatatypeException("Expected " + expected
+ " but found \u201c" + (char) ch + "\u201d instead "
+ "(context: \u201c" + context.toString() + "\u201d).");
} else {
throw newDatatypeException("Expected " + expected
+ " but value ended " + "(context: \u201c"
+ context.toString() + "\u201d).");
}
}
private void reportNonNumber(char command, int ch)
throws DatatypeException, IOException {
if (ch != -1) {
throw newDatatypeException("Expected number for \u201c" + command
+ "\u201d command but found " + "\u201c" + (char) ch
+ "\u201d instead " + "(context: \u201c"
+ context.toString() + "\u201d).");
} else {
throw newDatatypeException("Expected number for \u201c" + command
+ "\u201d command but value ended " + "(context: \u201c"
+ context.toString() + "\u201d).");
}
}
@Override public String getName() {
return "SVG path data";
}
}