[LIB-9] Set of entities for PGN parsing and formatting
authorgotty <gotty@hedgecode.org>
Fri, 24 Jan 2020 23:55:16 +0000 (02:55 +0300)
committergotty <gotty@hedgecode.org>
Fri, 24 Jan 2020 23:55:16 +0000 (02:55 +0300)
33 files changed:
chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNBuilder.java
chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNConstants.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNParser.java
chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNUtils.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/DetailGame.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/DetailMove.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Game.java [moved from chesshog-format/src/main/java/org/hedgecode/chess/img/ImageBuilder.java with 60% similarity]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Move.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Moves.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Tag.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Variation.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/AbstractMovesFormat.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/AbstractPGNFormat.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ExportMovesFormat.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ExportPGNFormat.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/MovesFormat.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/PGNFormat.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ReduceMovesFormat.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ReducePGNFormat.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/AbstractWrapper.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/ExportWrapper.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/LineWrapper.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/MovesWrapper.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/CommentToken.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/EmptyToken.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/MoveToken.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/MovesToken.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/TagToken.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/Token.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/Tokenizer.java [new file with mode: 0644]
chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/VariationToken.java [new file with mode: 0644]
chesshog-format/src/main/resources/org/hedgecode/chess/LocalStrings.properties
chesshog-format/src/main/resources/org/hedgecode/chess/LocalStrings_ru.properties

index 7095a56..5d57dcf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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();
+    }
 
 }
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNConstants.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNConstants.java
new file mode 100644 (file)
index 0000000..45b2408
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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())
+        );
+    }
+
+}
index 274bfdf..4f67a2d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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;
+    }
 
 }
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNUtils.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/PGNUtils.java
new file mode 100644 (file)
index 0000000..822c31f
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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())
+        );
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/DetailGame.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/DetailGame.java
new file mode 100644 (file)
index 0000000..7486dc0
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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;
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/DetailMove.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/DetailMove.java
new file mode 100644 (file)
index 0000000..2b37204
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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;
+    }
+
+}
@@ -1,5 +1,5 @@
 /*
- * 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();
 
 }
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Move.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Move.java
new file mode 100644 (file)
index 0000000..2b56f10
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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();
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Moves.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Moves.java
new file mode 100644 (file)
index 0000000..c751132
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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();
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Tag.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Tag.java
new file mode 100644 (file)
index 0000000..2c0a380
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * 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());
+            }
+        }
+
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Variation.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/entity/Variation.java
new file mode 100644 (file)
index 0000000..3c290e8
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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;
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/AbstractMovesFormat.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/AbstractMovesFormat.java
new file mode 100644 (file)
index 0000000..0240472
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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)
+        );
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/AbstractPGNFormat.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/AbstractPGNFormat.java
new file mode 100644 (file)
index 0000000..b5b0e47
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * 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();
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ExportMovesFormat.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ExportMovesFormat.java
new file mode 100644 (file)
index 0000000..4de3b41
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * 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
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ExportPGNFormat.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ExportPGNFormat.java
new file mode 100644 (file)
index 0000000..68e4a02
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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();
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/MovesFormat.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/MovesFormat.java
new file mode 100644 (file)
index 0000000..fbcc266
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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);
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/PGNFormat.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/PGNFormat.java
new file mode 100644 (file)
index 0000000..16e1de0
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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);
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ReduceMovesFormat.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ReduceMovesFormat.java
new file mode 100644 (file)
index 0000000..283dddd
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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());
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ReducePGNFormat.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/ReducePGNFormat.java
new file mode 100644 (file)
index 0000000..e5ef938
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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();
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/AbstractWrapper.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/AbstractWrapper.java
new file mode 100644 (file)
index 0000000..d39172b
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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();
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/ExportWrapper.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/ExportWrapper.java
new file mode 100644 (file)
index 0000000..67667a7
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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;
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/LineWrapper.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/LineWrapper.java
new file mode 100644 (file)
index 0000000..80cb3e7
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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;
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/MovesWrapper.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/format/wrap/MovesWrapper.java
new file mode 100644 (file)
index 0000000..809d5cb
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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);
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/CommentToken.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/CommentToken.java
new file mode 100644 (file)
index 0000000..660475a
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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();
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/EmptyToken.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/EmptyToken.java
new file mode 100644 (file)
index 0000000..838d020
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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;
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/MoveToken.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/MoveToken.java
new file mode 100644 (file)
index 0000000..e87bf9b
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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);
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/MovesToken.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/MovesToken.java
new file mode 100644 (file)
index 0000000..6667a27
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * 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;
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/TagToken.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/TagToken.java
new file mode 100644 (file)
index 0000000..91aa8f8
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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;
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/Token.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/Token.java
new file mode 100644 (file)
index 0000000..a0aa9f3
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/Tokenizer.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/Tokenizer.java
new file mode 100644 (file)
index 0000000..3421259
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * 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();
+            }
+        }
+    }
+
+}
diff --git a/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/VariationToken.java b/chesshog-format/src/main/java/org/hedgecode/chess/pgn/token/VariationToken.java
new file mode 100644 (file)
index 0000000..377df13
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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;
+    }
+
+}
index 66a5a66..67f3df1 100644 (file)
@@ -1,4 +1,4 @@
-# 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.
@@ -19,6 +19,14 @@ parse.null.input.string=Null input string
 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
index 05dad64..ce7ffa7 100644 (file)
@@ -1,4 +1,4 @@
-# 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.
@@ -18,6 +18,14 @@ parse.null.input.string=
 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=