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

src/Plugin/crossword/crossword_file_parser/IpuzParser.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 ipuz v1 or v2. http://www.ipuz.org/.
 *
 * There are many advanced features this does not support, like doing the
 * numbers in the grid out of order.
 *
 * @CrosswordFileParser(
 *   id = "ipuz",
 *   title = @Translation("ipuz")
 * )
 */
class IpuzParser extends CrosswordFileParserPluginBase {

  /**
   * {@inheritdoc}
   *
   * Checks for an ipuz file featuring correct version string.
   */
  public static function isApplicable(FileInterface $file) {

    if ($file->getMimeType() !== "application/octet-stream") {
      return FALSE;
    }

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

    $contents = file_get_contents($file->getFileUri());

    if (strpos($contents, 'ipuz.org/v') === FALSE) {
      return FALSE;
    };

    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function parse() {
    $decoded = json_decode($this->contents, TRUE);
    if (empty($decoded)) {
      throw new CrosswordException('The file is corrupted.');
    }
    $data = [
      'id' => $this->file->id(),
      'title' => $this->getTitle($decoded),
      'author' => $this->getAuthor($decoded),
      'notepad' => $this->getNotepad($decoded),
      'puzzle' => $this->getGridAndClues($decoded),
    ];

    return $data;
  }

  /**
   * Returns title of crossword.
   *
   * @param array $decoded
   *   The decoded json.
   *
   * @return string|null
   *   The title of the puzzle
   */
  protected function getTitle(array $decoded) {
    if (isset($decoded['title']) && is_string($decoded['title'])) {
      return $decoded['title'];
    }
  }

  /**
   * Returns author of crossword.
   *
   * @param array $decoded
   *   The decoded json.
   *
   * @return string|null
   *   The author of the puzzle
   */
  protected function getAuthor(array $decoded) {
    if (isset($decoded['author']) && is_string($decoded['author'])) {
      return $decoded['author'];
    }
  }

  /**
   * Returns notepad from crossword, which is the intro here.
   *
   * @param array $decoded
   *   The decoded json.
   *
   * @return string|null
   *   The notepad of the puzzle
   */
  protected function getNotepad(array $decoded) {
    if (isset($decoded['intro']) && is_string($decoded['intro'])) {
      return $decoded['intro'];
    }
  }

  /**
   * Returns grid and clues.
   *
   * @param array $decoded
   *   The decoded json.
   *
   * @return array
   *   Associative array containing nearly fully parsed grid and clues.
   */
  protected function getGridAndClues(array $decoded) {
    $grid = [];
    $clues = [
      'across' => [],
      'down' => [],
    ];

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

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

    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' => $fill && strlen($fill) > 1,
          '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'][$numeral])) {
                throw new CrosswordException('Number of across clues does not match size of grid.');
              }
              $clues['across'][] = [
                'text' => $raw_clues['across'][$numeral],
                'numeral' => $iterator['numeral'],
              ];
              unset($raw_clues['across'][$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'][$numeral])) {
                throw new CrosswordException('Number of down clues does not match size of grid.');
              }
              $clues['down'][] = [
                'text' => $raw_clues['down'][$numeral],
                'numeral' => $iterator['numeral'],
              ];
              unset($raw_clues['down'][$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'];
          }
        }
        $row[] = $square;
      }
      $grid[] = $row;
    }

    // Are there extra clues? The iterators should be at the end of the line.
    if (count($raw_clues['down']) > 0) {
      throw new CrosswordException('Number of down clues does not match size of grid.');
    }
    if (count($raw_clues['across']) > 0) {
      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 $decoded
   *   The decoded json.
   *
   * @return array
   *   Associative array containing an array of across clues and an array
   *   of down clues, with each clue keyed by its numeral.
   */
  protected function getRawClues(array $decoded) {
    if (empty($decoded['clues'])) {
      throw new CrosswordException('There are no clues.');
    }
    if (empty($decoded['clues']['Across'])) {
      throw new CrosswordException('The across clues are missing.');
    }
    if (empty($decoded['clues']['Down'])) {
      throw new CrosswordException('The down clues are missing.');
    }
    $clues = [
      'across' => [],
      'down' => [],
    ];
    foreach ($decoded['clues']['Across'] as $clue) {
      if (!is_array($clue)) {
        throw new CrosswordException('Each clue must be an array.');
      }
      $clues['across'][$clue[0]] = $clue[1];
    }
    foreach ($decoded['clues']['Down'] as $clue) {
      if (!is_array($clue)) {
        throw new CrosswordException('Each clue must be an array.');
      }
      $clues['down'][$clue[0]] = $clue[1];
    }
    return $clues;
  }

  /**
   * Returns a 2D array where each element is the text of a square.
   *
   * @param array $decoded
   *   The decoded json.
   *
   * @return array
   *   2D array where each element is the text of a square.
   */
  protected function getRawGrid(array $decoded) {
    if (empty($decoded['solution']) || !is_array($decoded['solution'])) {
      throw new CrosswordException('The solution is missing.');
    }
    if (empty($decoded['puzzle']) || !is_array($decoded['puzzle'])) {
      throw new CrosswordException('The grid is missing.');
    }
    $raw_grid = [];
    $first_row_columns = count($decoded['solution'][0]);
    // Iterate through solution squares.
    foreach ($decoded['solution'] as $row_index => $row) {
      if (count($row) !== $first_row_columns) {
        throw new CrosswordException('The grid is not rectangular.');
      }
      $raw_grid[$row_index] = [];
      foreach ($row as $col_index => $square) {
        $black = [NULL, '#'];
        if (in_array($square, $black)) {
          $raw_grid[$row_index][$col_index] = NULL;
        }
        else {
          if (isset($decoded['puzzle'][$row_index][$col_index]['style']['shapebg'])) {
            // We treat any bg as a circle. Like with Across Lite text, let's
            // make this lowercase in the raw grid.
            $raw_grid[$row_index][$col_index] = strtolower($square);
          }
          else {
            $raw_grid[$row_index][$col_index] = strtoupper($square);
          }
        }
      }
    }
    return $raw_grid;
  }

}

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

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