crossword-8.x-1.x-dev/src/Plugin/crossword/crossword_file_parser/AcrossLiteTextParser.php

src/Plugin/crossword/crossword_file_parser/AcrossLiteTextParser.php
<?php

namespace Drupal\crossword\Plugin\crossword\crossword_file_parser;

use Drupal\crossword\CrosswordException;
use Drupal\crossword\CrosswordFileParserPluginBase;
use Drupal\file\FileInterface;

/**
 * Plugin for parsing files in the Across Lite Text format v1 or v2.
 *
 * @CrosswordFileParser(
 *   id = "across_lite_text",
 *   title = @Translation("Across Lite Text")
 * )
 */
class AcrossLiteTextParser extends CrosswordFileParserPluginBase {

  /**
   * {@inheritdoc}
   *
   * Checks for a text file featuring an ACROSS PUZZLE tag.
   */
  public static function isApplicable(FileInterface $file) {

    if ($file->getMimeType() !== "text/plain") {
      return FALSE;
    }

    if (strpos($file->getFilename(), ".txt") === FALSE) {
      return FALSE;
    }

    $contents = file_get_contents($file->getFileUri());
    $contents = trim($contents);
    // This can also parse ACROSS PUZZLE 2.
    if (strpos($contents, '<ACROSS PUZZLE') === FALSE) {
      return FALSE;
    }

    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function parse() {

    $lines = explode("\n", $this->contents);
    foreach ($lines as &$line) {
      $line = trim($line);
    }
    $pre_parse = $this->parseTags($lines);
    $data = [
      'id' => $this->file->id(),
      'title' => $this->getTitle($pre_parse),
      'author' => $this->getAuthor($pre_parse),
      'notepad' => $this->getNotepad($pre_parse),
      'puzzle' => $this->getGridAndClues($pre_parse),
    ];

    return $data;
  }

  /**
   * Convert the txt file into a n associative array, more or less.
   *
   * @param array $lines
   *   The trimmed lines of the file.
   *
   * @return array
   *   An array containing a rudimentary parsing of the crossword.
   */
  protected function parseTags(array $lines) {
    $tags = [
      'title' => '<TITLE>',
      'author' => '<AUTHOR>',
      'copyright' => '<COPYRIGHT>',
      'size' => '<SIZE>',
      'grid' => '<GRID>',
      'rebus' => '<REBUS>',
      'across' => '<ACROSS>',
      'down' => '<DOWN>',
      'notepad' => '<NOTEPAD>',
    ];
    $pre_parse = [];
    $current_tag = '';
    foreach ($lines as $line) {
      if (!empty($line) && in_array($line, $tags, TRUE)) {
        $current_tag = array_search($line, $tags);
      }
      elseif ($current_tag) {
        $pre_parse[$current_tag][] = $line;
      }
    }

    if (!isset($pre_parse['grid']) || empty($pre_parse['grid'])) {
      throw new CrosswordException('The grid is missing.');
    }
    if (!isset($pre_parse['across']) || empty($pre_parse['across'])) {
      throw new CrosswordException('The across clues are missing.');
    }
    if (!isset($pre_parse['down']) || empty($pre_parse['down'])) {
      throw new CrosswordException('The down clues are missing.');
    }

    return $pre_parse;
  }

  /**
   * Returns title of crossword.
   *
   * @param array $pre_parse
   *   An array containing a rudimentary parsing of the crossword.
   *
   * @return string|null
   *   The title of the puzzle
   */
  protected function getTitle(array $pre_parse) {
    if (isset($pre_parse['title'])) {
      return $pre_parse['title'][0];
    }
  }

  /**
   * Returns author of crossword.
   *
   * @param array $pre_parse
   *   An array containing a rudimentary parsing of the crossword.
   *
   * @return string|null
   *   The author of the puzzle
   */
  protected function getAuthor(array $pre_parse) {
    if (isset($pre_parse['author'])) {
      return $pre_parse['author'][0];
    }
  }

  /**
   * Returns notepad from crossword.
   *
   * @param array $pre_parse
   *   An array containing a rudimentary parsing of the crossword.
   *
   * @return string|null
   *   The notepad of the puzzle
   */
  protected function getNotepad(array $pre_parse) {
    if (isset($pre_parse['notepad'])) {
      return $pre_parse['notepad'][0];
    }
  }

  /**
   * Returns grid and clues.
   *
   * When returns, the squares don't have moves and the references
   * don't have the index added yet.
   *
   * @param array $pre_parse
   *   An array containing a rudimentary parsing of the crossword.
   *
   * @return array
   *   Associative array containing nearly fully parsed grid and clues.
   */
  protected function getGridAndClues(array $pre_parse) {
    $grid = [];
    $clues = [
      'across' => [],
      'down' => [],
    ];

    $raw_clues = $this->getRawClues($pre_parse);
    $raw_grid = $this->getRawGrid($pre_parse);

    $iterator = [
      'index_across' => -1,
      'index_down' => -1,
      'numeral' => 0,
    ];

    $rebus_array = $this->getRebusArray($pre_parse);

    foreach ($raw_grid as $row_index => $raw_row) {
      $row = [];
      for ($col_index = 0; $col_index < count($raw_row); $col_index++) {
        $fill = $raw_row[$col_index];
        // In text, lowercase letters indicate a circle.
        $circle = $fill && strtoupper($fill) != $fill;
        $square = [
          'row' => $row_index,
          'col' => $col_index,
          'circle' => $circle,
          'rebus' => FALSE,
          'fill' => $fill === NULL ? NULL : strtoupper($fill),
        ];
        if ($fill !== NULL) {
          // Init some things to NULL.
          $numeral_incremented = FALSE;
          $numeral = NULL;

          // This will be the first square in an across clue if it is...
          // 1. the left square or to the right of a black
          // AND
          // 2. not the right square and the square to its right is not black.
          if ($col_index == 0 || $raw_row[$col_index - 1] === NULL) {
            if (isset($raw_row[$col_index + 1]) && $raw_row[$col_index + 1] !== NULL) {
              $iterator['index_across']++;
              $iterator['numeral']++;
              $numeral = $iterator['numeral'];
              if (!isset($raw_clues['across'][$iterator['index_across']])) {
                throw new CrosswordException('Number of across clues does not match size of grid.');
              }
              $clues['across'][] = [
                'text' => $raw_clues['across'][$iterator['index_across']],
                'numeral' => $iterator['numeral'],
              ];
              $numeral_incremented = TRUE;

              $square['across'] = [
                'index' => $iterator['index_across'],
              ];
              $square['numeral'] = $numeral;
            }
            else {
              // In here? It's an uncrossed square. No across clue. No numeral.
            }
          }
          else {
            // In here? No numeral.
            $square['across'] = [
              'index' => $iterator['index_across'],
            ];
          }

          // This will be the first square in a down clue if...
          // 1. It's the top square or the below a black
          // AND
          // 2. It's not the bottom square and the square below it is not black.
          if ($row_index == 0 || $raw_grid[$row_index - 1][$col_index] === NULL) {
            if (isset($raw_grid[$row_index + 1][$col_index]) && $raw_grid[$row_index + 1][$col_index] !== NULL) {
              $iterator['index_down']++;
              if (!$numeral_incremented) {
                $iterator['numeral']++;
              }
              $numeral = $iterator['numeral'];
              if (!isset($raw_clues['down'][$iterator['index_down']])) {
                throw new CrosswordException('Number of down clues does not match size of grid.');
              }
              $clues['down'][] = [
                'text' => $raw_clues['down'][$iterator['index_down']],
                'numeral' => $iterator['numeral'],
              ];
              $numeral_incremented = TRUE;

              $square['down'] = [
                'index' => $iterator['index_down'],
              ];
              $square['numeral'] = $numeral;
            }
            else {
              // In here? It's an uncrossed square. No down clue. No numeral.
            }
          }
          else {
            // In here? No numeral. Take the down value from the square above.
            $square['down'] = $grid[$row_index - 1][$col_index]['down'];
          }
        }

        // Is it a rebus square?
        if (is_numeric($square['fill']) && !empty($rebus_array) && isset($rebus_array[$square['fill']])) {
          $square['fill'] = $rebus_array[$square['fill']];
          $square['rebus'] = TRUE;
        }

        $row[] = $square;
      }
      $grid[] = $row;
    }

    // Are there extra clues? The iterators should be at the end of the line.
    if (count($raw_clues['down']) - 1 > $iterator['index_down']) {
      throw new CrosswordException('Number of down clues does not match size of grid.');
    }
    if (count($raw_clues['across']) - 1 > $iterator['index_across']) {
      throw new CrosswordException('Number of across clues does not match size of grid.');
    }

    return [
      'grid' => $grid,
      'clues' => $clues,
    ];
  }

  /**
   * Returns an array of arrays of clue text.
   *
   * @param array $pre_parse
   *   An array containing a rudimentary parsing of the crossword.
   *
   * @return array
   *   Associative array containing an array of across clue text and an array
   *   of down clue text.
   */
  protected function getRawClues(array $pre_parse) {
    return [
      'across' => $pre_parse['across'],
      'down' => $pre_parse['down'],
    ];
  }

  /**
   * Returns a 2D array where each element is the text of a square.
   *
   * @param array $pre_parse
   *   An array containing a rudimentary parsing of the crossword.
   *
   * @return array
   *   2D array where each element is the text of a square.
   */
  protected function getRawGrid(array $pre_parse) {
    $raw_grid = [];
    $grid_lines = $pre_parse['grid'];
    $first_row_columns = strlen($grid_lines[0]);
    foreach ($grid_lines as $row_index => $grid_line) {
      if (strlen($grid_line) !== $first_row_columns) {
        throw new CrosswordException('The grid is not rectangular.');
      }
      for ($col_index = 0; $col_index < strlen($grid_line); $col_index++) {
        $raw_grid[$row_index][] = $grid_line[$col_index] !== "." ? $grid_line[$col_index] : NULL;
      }
    }
    return $raw_grid;
  }

  /**
   * Returns array used to handle rebus puzzles.
   *
   * @param array $pre_parse
   *   An array containing a rudimentary parsing of the crossword.
   *
   * @return array
   *   An associative array. The key is a number. The corresponding value
   *   is the text that should replace the number any time it appears in the
   *   grid.
   */
  protected function getRebusArray(array $pre_parse) {
    if (isset($pre_parse['rebus'])) {
      $rebus_array = [];
      foreach ($pre_parse['rebus'] as $line) {
        $parts = explode(':', $line);
        if (is_numeric($parts[0]) && isset($parts[1])) {
          $rebus_array[$parts[0]] = $parts[1];
        }
      }
      return $rebus_array;
    }
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc