/*
- * Copyright (c) 2018. Developed by Hedgecode.
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
package org.hedgecode.chess.pgn;
+import org.hedgecode.chess.pgn.entity.Game;
+import org.hedgecode.chess.pgn.format.PGNFormat;
+import org.hedgecode.chess.pgn.format.ReducePGNFormat;
+
/**
- *
+ * PGNBuilder
*
* @author Dmitry Samoshin aka gotty
*/
public class PGNBuilder {
+ public static String build(Game game) {
+ return build(
+ game, new ReducePGNFormat()
+ );
+ }
-
+ public static String build(Game game, PGNFormat pgnFormat) {
+ pgnFormat.addTags(
+ game.getTags()
+ );
+ pgnFormat.addMoves(
+ game.getMoves()
+ );
+ return pgnFormat.format();
+ }
}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn;
+
+/**
+ * PGNConstants
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public final class PGNConstants {
+
+ public static final int PGN_DEF_LINE_LENGTH = 80;
+ public static final int PGN_MAX_LINE_LENGTH = 255;
+
+ public static final String PGN_SPACE = " ";
+ public static final String PGN_CRLF = "\n";
+
+ public static final String PGN_DETECT_REGEX = "^\\[Event \"[^\"]+\"\\]$";
+
+ public static final String WHITE_MOVE_FORMAT = "%d.%s";
+ public static final String BLACK_MOVE_FORMAT = "%s";
+ public static final String BLACK_MOVE_DOT_FORMAT = "%d...%s";
+
+ public static final String COMMENT_FORMAT = "{%s}";
+ public static final String VARIATION_FORMAT = "(%s)";
+
+ private PGNConstants() {
+ throw new AssertionError(
+ String.format("No %s instances!", getClass().getName())
+ );
+ }
+
+}
/*
- * Copyright (c) 2018. Developed by Hedgecode.
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
package org.hedgecode.chess.pgn;
+import org.hedgecode.chess.pgn.entity.Game;
+import org.hedgecode.chess.pgn.token.Tokenizer;
+import org.hedgecode.chess.pgn.entity.DetailGame;
+import org.hedgecode.chess.position.ParseException;
+
/**
- *
+ * PGNParser
*
* @author Dmitry Samoshin aka gotty
*/
public class PGNParser {
-
+ public static Game parse(String pgn) throws ParseException {
+ Game game = new DetailGame();
+ Tokenizer pgnTokenizer = new Tokenizer(pgn);
+ while (pgnTokenizer.hasToken()) {
+ pgnTokenizer.token(game);
+ }
+ return game;
+ }
}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * PGNUtils
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public final class PGNUtils {
+
+ private static final char BACKSLASH = '\\';
+ private static final String SHIELD_REGEX = "\\\\";
+
+ private static final String CRLF = "\\r?\\n";
+ private static final String SPACE = " ";
+
+ public static String match(String source, String regex) {
+ Matcher matcher = Pattern.compile(
+ regex,
+ Pattern.MULTILINE
+ ).matcher(source);
+ if (matcher.find()) {
+ return matcher.groupCount() > 0
+ ? matcher.group(1)
+ : matcher.group();
+ }
+ return null;
+ }
+
+ public static boolean isPgn(String source) {
+ return match(
+ source,
+ PGNConstants.PGN_DETECT_REGEX
+ ) != null;
+ }
+
+ public static String shield(String source, char[] shields) {
+ for (char shield : shields) {
+ if (source.indexOf(shield) >= 0) {
+ String regexShield =
+ shield == BACKSLASH
+ ? SHIELD_REGEX
+ : String.valueOf(shield);
+ source = source.replaceAll(
+ String.format("([%s])", regexShield),
+ SHIELD_REGEX.concat("$1")
+ );
+ }
+ }
+ return source;
+ }
+
+ public static String stripCrlf(String pgn) {
+ return pgn.replaceAll(CRLF, SPACE);
+ }
+
+ private PGNUtils() {
+ throw new AssertionError(
+ String.format("No %s instances!", getClass().getName())
+ );
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.entity;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * DetailGame
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class DetailGame implements Game, Moves {
+
+ private final DetailMove NULL_MOVE = new DetailMove(0, null);
+
+ private DetailMove currentMove;
+ private final List<DetailMove> moves = new ArrayList<>();
+
+ private final Map<String, String> tags = new HashMap<>();
+
+ public DetailGame() {
+ currentMove = NULL_MOVE;
+ }
+
+ @Override
+ public void addMove(DetailMove move) {
+ moves.add(move);
+ currentMove = move;
+ }
+
+ @Override
+ public List<DetailMove> getMoves() {
+ return moves;
+ }
+
+ @Override
+ public DetailMove nullMove() {
+ return NULL_MOVE;
+ }
+
+ @Override
+ public DetailMove currentMove() {
+ return currentMove;
+ }
+
+ @Override
+ public void addTag(String name, String value) {
+ tags.put(name, value);
+ }
+
+ @Override
+ public String getTag(String name) {
+ return tags.get(name);
+ }
+
+ @Override
+ public Map<String, String> getTags() {
+ return tags;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.entity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * DetailMove
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class DetailMove implements Move {
+
+ private int ply;
+ private String move;
+
+ private List<String> comments = new ArrayList<>();
+ private List<Variation> variations = new ArrayList<>();
+
+ public DetailMove(int ply, String move) {
+ this.ply = ply;
+ this.move = move;
+ }
+
+ @Override
+ public int ply() {
+ return ply;
+ }
+
+ @Override
+ public String move() {
+ return move;
+ }
+
+ public void addComment(String comment) {
+ comments.add(comment);
+ }
+
+ public void addVariation(Variation variation) {
+ variations.add(variation);
+ }
+
+ public List<String> comments() {
+ return comments;
+ }
+
+ public List<Variation> variations() {
+ return variations;
+ }
+
+}
/*
- * Copyright (c) 2018. Developed by Hedgecode.
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* limitations under the License.
*/
-package org.hedgecode.chess.img;
+package org.hedgecode.chess.pgn.entity;
-import java.awt.image.RenderedImage;
-
-import org.hedgecode.chess.position.Builder;
-import org.hedgecode.chess.position.Position;
+import java.util.Map;
/**
- *
+ * Game
*
* @author Dmitry Samoshin aka gotty
*/
-public interface ImageBuilder extends Builder {
+public interface Game extends Moves {
+
+ void addTag(String name, String value);
- String build(Position position);
+ String getTag(String name);
- RenderedImage build(Position position, String boardType, String pieceType) throws ImageException;
+ Map<String, String> getTags();
}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.entity;
+
+/**
+ * Move
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public interface Move extends Comparable<Move> {
+
+ int ply();
+
+ String move();
+
+ @Override
+ default int compareTo(Move move) {
+ return this.ply() - move.ply();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.entity;
+
+import java.util.List;
+
+/**
+ * Moves
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public interface Moves {
+
+ void addMove(DetailMove move);
+
+ List<DetailMove> getMoves();
+
+ DetailMove nullMove();
+
+ DetailMove currentMove();
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.entity;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.hedgecode.chess.pgn.PGNUtils;
+
+/**
+ * Tag
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public enum Tag {
+
+ EVENT ( "Event", true, Tag.QUESTION ),
+ SITE ( "Site", true, Tag.QUESTION ),
+ DATE ( "Date", true, Tag.QUESTION_DATE ),
+ ROUND ( "Round", true, Tag.QUESTION ),
+ WHITE ( "White", true, Tag.QUESTION ),
+ BLACK ( "Black", true, Tag.QUESTION ),
+ RESULT ( "Result", true, Tag.NON_RESULT ),
+ WHITE_TITLE ( "WhiteTitle", false, Tag.HYPHEN ),
+ BLACK_TITLE ( "BlackTitle", false, Tag.HYPHEN ),
+ WHITE_ELO ( "WhiteElo", false, Tag.HYPHEN ),
+ BLACK_ELO ( "BlackElo", false, Tag.HYPHEN ),
+ WHITE_USCF ( "WhiteUSCF", false, Tag.HYPHEN ),
+ BLACK_USCF ( "BlackUSCF", false, Tag.HYPHEN ),
+ WHITE_NA ( "WhiteNA", false, Tag.HYPHEN ),
+ BLACK_NA ( "BlackNA", false, Tag.HYPHEN ),
+ WHITE_TYPE ( "WhiteType", false, Tag.HYPHEN ),
+ BLACK_TYPE ( "BlackType", false, Tag.HYPHEN ),
+ EVENT_DATE ( "EventDate", false, Tag.QUESTION_DATE ),
+ EVENT_SPONSOR ( "EventSponsor", false, Tag.EMPTY ),
+ SECTION ( "Section", false, Tag.EMPTY ),
+ STAGE ( "Stage", false, Tag.EMPTY ),
+ BOARD ( "Board", false, Tag.EMPTY ),
+ OPENING ( "Opening", false, Tag.EMPTY ),
+ VARIATION ( "Variation", false, Tag.EMPTY ),
+ SUBVARIATION ( "SubVariation", false, Tag.EMPTY ),
+ ECO ( "ECO", false, Tag.EMPTY ),
+ NIC ( "NIC", false, Tag.EMPTY ),
+ TIME ( "Time", false, Tag.QUESTION_TIME ),
+ UTC_TIME ( "UTCTime", false, Tag.QUESTION_TIME ),
+ UTC_DATE ( "UTCDate", false, Tag.QUESTION_DATE ),
+ TIME_CONTROL ( "TimeControl", false, Tag.QUESTION ),
+ SETUP ( "SetUp", false, Tag.ZERO ),
+ FEN ( "FEN", false, Tag.EMPTY ),
+ TERMINATION ( "Termination", false, Tag.EMPTY ),
+ ANNOTATOR ( "Annotator", false, Tag.EMPTY ),
+ MODE ( "Mode", false, Tag.EMPTY ),
+ PLY_COUNT ( "PlyCount", false, Tag.EMPTY );
+
+ // todo: -> PGNConstants
+ public static final String EMPTY = "";
+ public static final String HYPHEN = "-";
+ public static final String ZERO = "0";
+ public static final String QUESTION = "?";
+ public static final String QUESTION_DATE = "????.??.??";
+ public static final String QUESTION_TIME = "??:??:??";
+ public static final String NON_RESULT = "*";
+
+ public static final String TAG_FORMAT = "[%s \"%s\"]";
+
+ public static final char[] TAG_SHIELD_CHARS = { '\\', '"' };
+
+ private String tagName;
+ private boolean isRequired;
+ private String defaultValue;
+
+ Tag(String name, boolean required, String defValue) {
+ tagName = name;
+ isRequired = required;
+ defaultValue = defValue;
+ }
+
+ public String getName() {
+ return tagName;
+ }
+
+ public boolean isRequired() {
+ return isRequired;
+ }
+
+ public String defaultValue() {
+ return defaultValue;
+ }
+
+ public static String formatTagValue(String tagValue) {
+ return PGNUtils.shield(
+ tagValue, TAG_SHIELD_CHARS
+ );
+ }
+
+ public static Tag[] tags() {
+ Tag[] tags = values();
+ Arrays.sort(
+ tags, new TagComparator()
+ );
+ return tags;
+ }
+
+ static class TagComparator implements Comparator<Tag> {
+
+ @Override
+ public int compare(Tag tag1, Tag tag2) {
+ if (tag1.isRequired && tag2.isRequired) {
+ return tag1.ordinal() - tag2.ordinal();
+ } else {
+ return tag1.isRequired ? -1
+ : tag2.isRequired ? 1
+ : tag1.name().compareTo(tag2.name());
+ }
+ }
+
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.entity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Variation
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class Variation implements Moves {
+
+ private final DetailMove NULL_MOVE = new DetailMove(0, null);
+
+ private DetailMove currentMove;
+ private final List<DetailMove> moves = new ArrayList<>();
+
+ public Variation() {
+ currentMove = NULL_MOVE;
+ }
+
+ @Override
+ public void addMove(DetailMove move) {
+ moves.add(move);
+ currentMove = move;
+ }
+
+ @Override
+ public List<DetailMove> getMoves() {
+ return moves;
+ }
+
+ @Override
+ public DetailMove nullMove() {
+ return NULL_MOVE;
+ }
+
+ @Override
+ public DetailMove currentMove() {
+ return currentMove;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.format;
+
+import java.util.Arrays;
+
+import org.hedgecode.chess.pgn.entity.Move;
+
+/**
+ * AbstractMovesFormat
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public abstract class AbstractMovesFormat implements MovesFormat {
+
+ @Override
+ public String format(Move[] moves) {
+ return format(
+ Arrays.asList(moves)
+ );
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.format;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.hedgecode.chess.pgn.PGNUtils;
+import org.hedgecode.chess.pgn.entity.Move;
+import org.hedgecode.chess.pgn.entity.Tag;
+import org.hedgecode.chess.pgn.format.wrap.ExportWrapper;
+import org.hedgecode.chess.pgn.format.wrap.MovesWrapper;
+
+import static org.hedgecode.chess.pgn.PGNConstants.*;
+
+/**
+ * AbstractPGNFormat
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public abstract class AbstractPGNFormat<Format extends MovesFormat> implements PGNFormat<Format> {
+
+ private final DateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd");
+ private final DateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
+
+ private final Map<Tag, String> pgnTags = new HashMap<>();
+
+ private String pgnMoves;
+ private List<Move> moves;
+
+ private MovesWrapper wrapper;
+
+ public AbstractPGNFormat() {
+ this.wrapper = new ExportWrapper();
+ }
+
+ public AbstractPGNFormat(MovesWrapper wrapper) {
+ this.wrapper = wrapper;
+ }
+
+ @Override
+ public void addTag(Tag tag, String value) {
+ pgnTags.put(tag, value);
+ }
+
+ @Override
+ public void addTags(Map<String, String> tags) {
+ for (Tag tag : Tag.tags()) {
+ String value = tags.get(tag.getName());
+ if (value != null) {
+ pgnTags.put(tag, value);
+ }
+ }
+ }
+
+ @Override
+ public String getTag(Tag tag) {
+ return pgnTags.get(tag);
+ }
+
+ @Override
+ public void addMoves(String moves) {
+ pgnMoves = moves;
+ }
+
+ @Override
+ public void addMoves(Move[] moves) {
+ this.moves = Arrays.asList(moves);
+ }
+
+ @Override
+ public void addMoves(List<Move> moves) {
+ this.moves = moves;
+ }
+
+ @Override
+ public String formatDate(Date date) {
+ return dateFormat.format(date);
+ }
+
+ @Override
+ public String formatTime(Date time) {
+ return timeFormat.format(time);
+ }
+
+ protected String formatMoves() {
+ if (pgnMoves == null) {
+ pgnMoves = movesFormat().format(moves);
+ } else {
+ pgnMoves = PGNUtils.stripCrlf(pgnMoves);
+ }
+ return wrapper.wrap(
+ pgnMoves.concat(
+ PGN_SPACE
+ ).concat(
+ result()
+ )
+ );
+ }
+
+ private String result() {
+ String result = pgnTags.get(Tag.RESULT);
+ return result != null ? result : Tag.RESULT.defaultValue();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.format;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.hedgecode.chess.pgn.entity.DetailMove;
+import org.hedgecode.chess.pgn.entity.Move;
+import org.hedgecode.chess.pgn.entity.Moves;
+import org.hedgecode.chess.pgn.entity.Variation;
+
+import static org.hedgecode.chess.pgn.PGNConstants.*;
+
+/**
+ * ExportMovesFormat
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class ExportMovesFormat extends AbstractMovesFormat {
+
+ @Override
+ public String format(List<Move> moves) {
+ Collections.sort(moves);
+ StringBuilder sb = new StringBuilder();
+ Move prevMove = null;
+ for (Move move : moves) {
+ sb.append(
+ formatSan(move, prevMove)
+ ).append(
+ PGN_SPACE
+ );
+ if (move instanceof DetailMove) {
+ DetailMove detailMove = (DetailMove) move;
+ sb.append(
+ formatAddition(detailMove)
+ );
+ }
+ prevMove = move;
+ }
+ return sb.toString().trim();
+ }
+
+ @Override
+ public String format(Moves moves) {
+ Collections.sort(moves.getMoves());
+ StringBuilder sb = new StringBuilder();
+ sb.append(
+ formatComment(moves.nullMove())
+ );
+ DetailMove prevMove = null;
+ for (DetailMove move : moves.getMoves()) {
+ sb.append(
+ formatSan(move, prevMove)
+ ).append(
+ PGN_SPACE
+ ).append(
+ formatAddition(move)
+ );
+ prevMove = move;
+ }
+ return sb.toString().trim();
+ }
+
+ private String formatSan(Move move, Move prevMove) {
+ return move.ply() % 2 != 0
+ ? whiteFormat(move)
+ : blackFormat(move, prevMove);
+ }
+
+ private String whiteFormat(Move move) {
+ return String.format(
+ WHITE_MOVE_FORMAT, move.ply() / 2 + 1, move.move()
+ );
+ }
+
+ private String blackFormat(Move move, Move prevMove) {
+ if (prevMove != null && prevMove instanceof DetailMove) {
+ DetailMove detailMove = (DetailMove) prevMove;
+ if (detailMove.comments().size() > 0 || detailMove.variations().size() > 0) {
+ return String.format(
+ BLACK_MOVE_DOT_FORMAT, move.ply() / 2, move.move()
+ );
+ }
+ }
+ return String.format(BLACK_MOVE_FORMAT, move.move());
+ }
+
+ private String formatAddition(DetailMove move) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(
+ formatComment(move)
+ ).append(
+ formatVariation(move)
+ );
+ return sb.toString();
+ }
+
+ private String formatComment(DetailMove move) {
+ StringBuilder sb = new StringBuilder();
+ for (String comment : move.comments()) {
+ sb.append(
+ String.format(COMMENT_FORMAT, comment)
+ ).append(PGN_SPACE);
+ }
+ return sb.toString();
+ }
+
+ private String formatVariation(DetailMove move) {
+ StringBuilder sb = new StringBuilder();
+ for (Variation variation : move.variations()) {
+ sb.append(
+ String.format(VARIATION_FORMAT, format(variation))
+ ).append(PGN_SPACE);
+ }
+ return sb.toString();
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.format;
+
+import org.hedgecode.chess.pgn.entity.Tag;
+
+import static org.hedgecode.chess.pgn.PGNConstants.*;
+
+/**
+ * ExportPGNFormat
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class ExportPGNFormat extends AbstractPGNFormat<ExportMovesFormat> {
+
+ @Override
+ public String format() {
+ StringBuilder sb = new StringBuilder();
+ for (Tag tag : Tag.tags()) {
+ String tagValue = getTag(tag);
+ if (tag.isRequired() || tagValue != null) {
+ sb.append(
+ String.format(
+ Tag.TAG_FORMAT,
+ tag.getName(),
+ tagValue != null
+ ? Tag.formatTagValue(tagValue)
+ : tag.defaultValue()
+ )
+ ).append(PGN_CRLF);
+ }
+ }
+ sb.append(PGN_CRLF).append(
+ formatMoves()
+ ).append(PGN_CRLF);
+ return sb.toString();
+ }
+
+ @Override
+ public ExportMovesFormat movesFormat() {
+ return new ExportMovesFormat();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.format;
+
+import java.util.List;
+
+import org.hedgecode.chess.pgn.entity.Move;
+import org.hedgecode.chess.pgn.entity.Moves;
+
+/**
+ * MovesFormat
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public interface MovesFormat {
+
+ String format(Move[] moves);
+
+ String format(List<Move> moves);
+
+ String format(Moves moves);
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.format;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.hedgecode.chess.pgn.entity.Move;
+import org.hedgecode.chess.pgn.entity.Tag;
+
+/**
+ * PGNFormat
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public interface PGNFormat<Format extends MovesFormat> {
+
+ String format();
+
+ void addTag(Tag tag, String value);
+
+ void addTags(Map<String, String> tags);
+
+ String getTag(Tag tag);
+
+ void addMoves(String moves);
+
+ void addMoves(Move[] moves);
+
+ void addMoves(List<Move> moves);
+
+ Format movesFormat();
+
+ String formatDate(Date date);
+
+ String formatTime(Date time);
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.format;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.hedgecode.chess.pgn.entity.Move;
+import org.hedgecode.chess.pgn.entity.Moves;
+
+import static org.hedgecode.chess.pgn.PGNConstants.*;
+
+/**
+ * ReduceMovesFormat
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class ReduceMovesFormat extends AbstractMovesFormat {
+
+ @Override
+ public String format(List<Move> moves) {
+ Collections.sort(moves);
+ StringBuilder sb = new StringBuilder();
+ for (Move move : moves) {
+ sb.append(
+ formatSan(move)
+ ).append(
+ PGN_SPACE
+ );
+ }
+ return sb.toString().trim();
+ }
+
+ @Override
+ public String format(Moves moves) {
+ return format(
+ moves.getMoves().toArray(
+ new Move[moves.getMoves().size()]
+ )
+ );
+ }
+
+ private String formatSan(Move move) {
+ return move.ply() % 2 != 0
+ ? String.format(WHITE_MOVE_FORMAT, move.ply() / 2 + 1, move.move())
+ : String.format(BLACK_MOVE_FORMAT, move.move());
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.format;
+
+import org.hedgecode.chess.pgn.entity.Tag;
+
+import static org.hedgecode.chess.pgn.PGNConstants.*;
+
+/**
+ * ReducePGNFormat
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class ReducePGNFormat extends AbstractPGNFormat<ReduceMovesFormat> {
+
+ @Override
+ public String format() {
+ StringBuilder sb = new StringBuilder();
+ for (Tag tag : Tag.tags()) {
+ String tagValue = getTag(tag);
+ if (tag.isRequired()) {
+ sb.append(
+ String.format(
+ Tag.TAG_FORMAT,
+ tag.getName(),
+ tagValue != null
+ ? Tag.formatTagValue(tagValue)
+ : tag.defaultValue()
+ )
+ ).append(PGN_CRLF);
+ }
+ }
+ sb.append(PGN_CRLF).append(
+ formatMoves()
+ ).append(PGN_CRLF);
+ return sb.toString();
+ }
+
+ @Override
+ public ReduceMovesFormat movesFormat() {
+ return new ReduceMovesFormat();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.format.wrap;
+
+import static org.hedgecode.chess.pgn.PGNConstants.*;
+
+/**
+ * AbstractWrapper
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public abstract class AbstractWrapper implements MovesWrapper {
+
+ protected abstract int length();
+
+ @Override
+ public String wrap(String moves) {
+ StringBuilder sb = new StringBuilder();
+ int startIndex = 0, endIndex = 0;
+ moves = moves.concat(PGN_SPACE);
+ int index = moves.indexOf(PGN_SPACE);
+ while (index >= 0) {
+ if (index - startIndex >= length()) {
+ sb.append(
+ moves.substring(startIndex, endIndex)
+ ).append(PGN_CRLF);
+ startIndex = endIndex + 1;
+ }
+ endIndex = index;
+ index = moves.indexOf(PGN_SPACE, index + 1);
+ }
+ sb.append(
+ moves.substring(startIndex)
+ ).append(PGN_CRLF);
+ return sb.toString();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.format.wrap;
+
+import org.hedgecode.chess.pgn.PGNConstants;
+
+/**
+ * ExportWrapper
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class ExportWrapper extends AbstractWrapper {
+
+ @Override
+ protected int length() {
+ return PGNConstants.PGN_DEF_LINE_LENGTH;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.format.wrap;
+
+import org.hedgecode.chess.pgn.PGNConstants;
+
+/**
+ * LineWrapper
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class LineWrapper extends AbstractWrapper {
+
+ @Override
+ protected int length() {
+ return PGNConstants.PGN_MAX_LINE_LENGTH;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.format.wrap;
+
+/**
+ * MovesWrapper
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public interface MovesWrapper {
+
+ String wrap(String moves);
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.token;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.hedgecode.chess.pgn.entity.Moves;
+import org.hedgecode.chess.position.ParseException;
+
+/**
+ * CommentToken
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class CommentToken implements Token<Moves> {
+
+ private static final String COMMENT_REGEX = "^\\s*\\{([^}]+)\\}";
+ private static final Pattern COMMENT_PATTERN = Pattern.compile(COMMENT_REGEX);
+ private static final int COMMENT_GROUP = 1;
+
+ @Override
+ public int token(Moves moves, String pgn) throws ParseException {
+ Matcher matcher = COMMENT_PATTERN.matcher(pgn);
+ if (!matcher.find()) {
+ throw new ParseException("parse.pgn.incorrect.comment", pgn);
+ } else {
+ String comment = matcher.group(COMMENT_GROUP);
+ moves.currentMove().addComment(comment);
+ }
+ return matcher.group().length();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.token;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.hedgecode.chess.pgn.PGNConstants;
+import org.hedgecode.chess.pgn.entity.Game;
+import org.hedgecode.chess.position.ParseException;
+
+/**
+ * EmptyToken
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class EmptyToken implements Token<Game> {
+
+ private static final String EMPTY_REGEX = "^\\s*$";
+ private static final Pattern EMPTY_PATTERN = Pattern.compile(EMPTY_REGEX, Pattern.MULTILINE);
+
+ @Override
+ public int token(Game game, String pgn) throws ParseException {
+ Matcher matcher = EMPTY_PATTERN.matcher(pgn);
+ if (!matcher.find()) {
+ throw new ParseException("parse.pgn.incorrect.empty", pgn);
+ }
+ return pgn.indexOf(PGNConstants.PGN_CRLF) + 1;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.token;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.hedgecode.chess.pgn.entity.DetailMove;
+import org.hedgecode.chess.pgn.entity.Moves;
+import org.hedgecode.chess.position.ParseException;
+
+/**
+ * MoveToken
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class MoveToken implements Token<Moves> {
+
+ private static final String MOVE_REGEX = "^\\s*(([0-9]+)\\s*(\\.|\\.{3})\\s*)*([PNBRQK]?[a-hx1-8\\-O]+(=[NBRQ])?([+#])?)";
+ private static final Pattern MOVE_PATTERN = Pattern.compile(MOVE_REGEX);
+ private static final int PLY_GROUP = 2, DOT_GROUP = 3, MOVE_GROUP = 4;
+ private static final String DOT_BLACK = "...";
+
+ @Override
+ public int token(Moves moves, String pgn) throws ParseException {
+ Matcher matcher = MOVE_PATTERN.matcher(pgn);
+ if (!matcher.find()) {
+ throw new ParseException("parse.pgn.incorrect.move", pgn);
+ } else {
+ boolean isBlackMove = isBlackMove(matcher.group(DOT_GROUP));
+ String plyStr = matcher.group(PLY_GROUP);
+ int ply = plyStr != null
+ ? Integer.parseInt(plyStr) * 2 - (isBlackMove ? 0 : 1)
+ : moves.currentMove().ply() + 1;
+ String move = matcher.group(MOVE_GROUP);
+ moves.addMove(
+ new DetailMove(ply, move)
+ );
+ }
+ return matcher.group().length();
+ }
+
+ private boolean isBlackMove(String dotGroup) {
+ return dotGroup == null || dotGroup.equals(DOT_BLACK);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.token;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.hedgecode.chess.pgn.PGNUtils;
+import org.hedgecode.chess.pgn.entity.Moves;
+import org.hedgecode.chess.position.ParseException;
+
+/**
+ * MovesToken
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class MovesToken<Entity extends Moves> implements Token<Entity> {
+
+ enum Type {
+
+ MOVE ( new MoveToken() ),
+ COMMENT ( new CommentToken() ),
+ VARIATION ( new VariationToken() );
+
+ private Token<Moves> token;
+
+ public Token<Moves> token() {
+ return token;
+ }
+
+ Type(Token<Moves> token) {
+ this.token = token;
+ }
+ }
+
+ private static final String MOVES_REGEX = "^\\s*([^\\s])";
+ private static final Pattern MOVES_PATTERN = Pattern.compile(MOVES_REGEX);
+
+ private static final String COMMENT = "{";
+ private static final String VARIATION = "(";
+
+ @Override
+ public int token(Entity entity, String pgn) throws ParseException {
+ int pgnLength = pgn.length();
+ pgn = PGNUtils.stripCrlf(pgn);
+ Token<Moves> token = assignToken(pgn);
+ while (token != null) {
+ int index = token.token(entity, pgn);
+ pgn = pgn.substring(index);
+ token = assignToken(pgn);
+ }
+ return pgnLength;
+ }
+
+ private Token<Moves> assignToken(String pgn) {
+ Token<Moves> token = null;
+ Matcher moveMatcher = MOVES_PATTERN.matcher(pgn);
+ if (moveMatcher.find()) {
+ switch (moveMatcher.group(1)) {
+ case COMMENT:
+ token = Type.COMMENT.token();
+ break;
+ case VARIATION:
+ token = Type.VARIATION.token();
+ break;
+ default:
+ token = Type.MOVE.token();
+ }
+ }
+ return token;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.token;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.hedgecode.chess.pgn.PGNConstants;
+import org.hedgecode.chess.pgn.entity.Game;
+import org.hedgecode.chess.position.ParseException;
+
+/**
+ * TagToken
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class TagToken implements Token<Game> {
+
+ private static final String TAG_REGEX = "^\\[([^\"]+)\\s\"([^\"]+)\"\\]$";
+ private static final Pattern TAG_PATTERN = Pattern.compile(TAG_REGEX, Pattern.MULTILINE);
+ private static final int NAME_GROUP = 1, VALUE_GROUP = 2;
+
+ @Override
+ public int token(Game game, String pgn) throws ParseException {
+ Matcher matcher = TAG_PATTERN.matcher(pgn);
+ if (!matcher.find()) {
+ throw new ParseException("parse.pgn.incorrect.tag", pgn);
+ } else {
+ String name = matcher.group(NAME_GROUP);
+ String value = matcher.group(VALUE_GROUP);
+ game.addTag(name, value);
+ }
+ return pgn.indexOf(PGNConstants.PGN_CRLF) + 1;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.token;
+
+import org.hedgecode.chess.position.ParseException;
+
+/**
+ * Token
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public interface Token<Entity> {
+
+ int token(Entity entity, String pgn) throws ParseException;
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.token;
+
+import java.util.regex.Pattern;
+
+import org.hedgecode.chess.pgn.entity.Game;
+import org.hedgecode.chess.position.ParseException;
+
+/**
+ * Tokenizer
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class Tokenizer {
+
+ enum Type {
+
+ TAG ( new TagToken() ),
+ EMPTY ( new EmptyToken() ),
+ MOVES ( new MovesToken() );
+
+ private Token<Game> token;
+
+ public Token<Game> token() {
+ return token;
+ }
+
+ Type(Token<Game> token) {
+ this.token = token;
+ }
+ }
+
+ private static final Pattern TAG_PATTERN = Pattern.compile("^\\[[^]]+\\]$", Pattern.MULTILINE);
+ private static final Pattern EMPTY_PATTERN = Pattern.compile("^\\s*$", Pattern.MULTILINE);
+ private static final Pattern MOVES_PATTERN = Pattern.compile("^\\s*([^\\s])", Pattern.MULTILINE);
+
+ private final String pgn;
+
+ private Token<Game> token;
+ private String tokenPgn;
+
+ public Tokenizer(String pgn) {
+ this.pgn = pgn;
+ this.tokenPgn = pgn;
+ }
+
+ public boolean hasToken() {
+ assignToken();
+ return token != null;
+ }
+
+ public void token(Game game) throws ParseException {
+ if (token == null) {
+ throw new ParseException("parse.pgn.null.token");
+ }
+ int index = token.token(game, tokenPgn);
+ tokenPgn = tokenPgn.substring(index);
+ token = null;
+ }
+
+ public String sourcePgn() {
+ return pgn;
+ }
+
+ private void assignToken() {
+ token = null;
+ if (!tokenPgn.isEmpty()) {
+ if (TAG_PATTERN.matcher(tokenPgn).find()) {
+ token = Type.TAG.token();
+ } else if (EMPTY_PATTERN.matcher(tokenPgn).find()) {
+ token = Type.EMPTY.token();
+ } else if (MOVES_PATTERN.matcher(tokenPgn).find()) {
+ token = Type.MOVES.token();
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2018-2020. Developed by Hedgecode.
+ *
+ * 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.hedgecode.chess.pgn.token;
+
+import java.util.regex.Pattern;
+
+import org.hedgecode.chess.pgn.entity.Moves;
+import org.hedgecode.chess.pgn.entity.Variation;
+import org.hedgecode.chess.position.ParseException;
+
+/**
+ * VariationToken
+ *
+ * @author Dmitry Samoshin aka gotty
+ */
+public class VariationToken implements Token<Moves> {
+
+ private static final char OPEN_VARIATION = '(';
+ private static final char CLOSE_VARIATION = ')';
+
+ private static final String VARIATION_REGEX = "^\\s*\\(([^)]+)\\)";
+ private static final Pattern VARIATION_PATTERN = Pattern.compile(VARIATION_REGEX);
+
+ @Override
+ public int token(Moves moves, String pgn) throws ParseException {
+ int startToken = pgn.indexOf(OPEN_VARIATION);
+ int endToken = endToken(pgn, startToken);
+ Variation variation = new Variation();
+ Token<Variation> variationToken = new MovesToken<>();
+ variationToken.token(
+ variation, pgn.substring(startToken + 1, endToken)
+ );
+ moves.currentMove().addVariation(variation);
+ return endToken + 1;
+ }
+
+ private int endToken(String pgn, int startToken) throws ParseException {
+ int open = pgn.indexOf(OPEN_VARIATION, startToken + 1);
+ int close = pgn.indexOf(CLOSE_VARIATION, startToken + 1);
+ while (open >= 0 && open < close) {
+ open = pgn.indexOf(OPEN_VARIATION, open + 1);
+ close = pgn.indexOf(CLOSE_VARIATION, close + 1);
+ }
+ if (close == -1) {
+ throw new ParseException("parse.pgn.incorrect.variation", pgn);
+ }
+ return close;
+ }
+
+}
-# Copyright (c) 2018. Developed by Hedgecode.
+# Copyright (c) 2018-2020. Developed by Hedgecode.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
parse.ascii.incorrect.board=Incorrect board
parse.fen.invalid.string=Invalid input
parse.fen.incorrect.board=Incorrect board
+
+parse.pgn.null.token=Unable to define token for PGN
+parse.pgn.incorrect.tag=Incorrect PGN tag
+parse.pgn.incorrect.empty=Missed blank line in PGN
+parse.pgn.incorrect.move=Incorrect PGN move
+parse.pgn.incorrect.comment=Incorrect PGN comment
+parse.pgn.incorrect.variation=Incorrect PGN variation
+
parse.tcd.index.out.of.bounds=Array Index Out Of Bounds
parse.tcd.invalid.bytes=Invalid input bytes
parse.tcd.incorrect.color=Incorrect color
-# Copyright (c) 2018. Developed by Hedgecode.
+# Copyright (c) 2018-2020. Developed by Hedgecode.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
parse.ascii.incorrect.board=
parse.fen.invalid.string=
parse.fen.incorrect.board=
+
+parse.pgn.null.token=
+parse.pgn.incorrect.tag=
+parse.pgn.incorrect.empty=
+parse.pgn.incorrect.move=
+parse.pgn.incorrect.comment=
+parse.pgn.incorrect.variation=
+
parse.tcd.index.out.of.bounds=
parse.tcd.invalid.bytes=
parse.tcd.incorrect.color=