All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.mdr.ascii.diagram.parser.BoxParser.scala Maven / Gradle / Ivy

The newest version!
package com.github.mdr.ascii.diagram.parser

import scala.annotation.tailrec
import scala.PartialFunction.cond
import com.github.mdr.ascii.common._
import com.github.mdr.ascii.common.Direction._
import com.github.mdr.ascii.layout.drawing.BoxDrawingCharacters._

/**
 * Responsible for detecting boxes in the diagram, e.g.
 *
 *   +---+      ╭───╮
 *   | A |  or  │ A │
 *   +---+      ╰───╯
 *
 */
trait BoxParser { self: DiagramParser ⇒

  protected def findAllBoxes: List[BoxImpl] =
    for {
      topLeft ← possibleTopLefts
      bottomRight ← completeBox(topLeft)
    } yield new BoxImpl(topLeft, bottomRight)

  private def possibleTopLefts: List[Point] =
    for {
      row ← (0 until numberOfRows - 1).toList
      column ← 0 until numberOfColumns - 1
      point = Point(row, column)
      cornerChar = charAt(point)
      if isTopLeftCorner(cornerChar)
      if isHorizontalBoxEdge(charAt(point go Right)) || isBoxDrawingCharacter(cornerChar)
      if isVerticalBoxEdge(charAt(point go Down)) || isBoxDrawingCharacter(cornerChar)
      // +-++-+
      // |A||B| Avoid closely-packed ASCII boxes parsing as 3 boxes, not 2
      // +-++-+
    } yield point

  @tailrec
  private def scanBoxEdge(p: Point, dir: Direction, isCorner: Char ⇒ Boolean, isEdge: Char ⇒ Boolean): Option[Point] =
    if (inDiagram(p)) {
      val c = charAt(p)
      if (isCorner(c))
        Some(p)
      else if (isEdge(c))
        scanBoxEdge(p.go(dir), dir, isCorner, isEdge)
      else
        None
    } else
      None // Can I use charAtOpt + flatMap and keep tailrec?

  /**
   * @return bottomRight of a box if all the edges are filled in correctly.
   */
  private def completeBox(topLeft: Point): Option[Point] =
    for {
      topRight ← scanBoxEdge(topLeft.right, Right, isTopRightCorner, isHorizontalBoxEdge)
      bottomRight ← scanBoxEdge(topRight.down, Down, isBottomRightCorner, isVerticalBoxEdge)
      bottomLeft ← scanBoxEdge(topLeft.down, Down, isBottomLeftCorner, isVerticalBoxEdge)
      bottomRight2 ← scanBoxEdge(bottomLeft.right, Right, isBottomRightCorner, isHorizontalBoxEdge)
      if bottomRight == bottomRight2 // sanity check
    } yield bottomRight

  private def isTopRightCorner(c: Char): Boolean = cond(c) { case '╗' | '╮' | '┐' | '+' ⇒ true }

  private def isBottomRightCorner(c: Char): Boolean = cond(c) { case '╝' | '╯' | '┘' | '+' ⇒ true }

  private def isTopLeftCorner(c: Char): Boolean = cond(c) { case '╔' | '╭' | '┌' | '+' ⇒ true }

  private def isBottomLeftCorner(c: Char): Boolean = cond(c) { case '╚' | '╰' | '└' | '+' ⇒ true }

  private def isHorizontalBoxEdge(c: Char): Boolean = cond(c) {
    case '═' | '─' | '-' | '╤' | '┬' | '╧' | '┴' | '╪' | '┼' ⇒ true
  }

  private def isVerticalBoxEdge(c: Char): Boolean = cond(c) {
    case '║' | '│' | '|' | '╢' | '┤' | '╟' | '├' | '╫' | '┼' ⇒ true
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy