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

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

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

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

class DiagramParser(s: String)
    extends UnicodeEdgeParser
    with DiagramImplementation
    with BoxParser
    with AsciiEdgeParser
    with LabelParser {

  private val rawRows: List[String] = if (s.isEmpty) Nil else s.split("(\r)?\n").toList

  protected val numberOfColumns = if (rawRows.isEmpty) 0 else rawRows.map(_.length).max

  private val rows = rawRows.map(_.padTo(numberOfColumns, ' ')).toArray

  protected val numberOfRows = rows.length

  protected val diagramRegion = Region(Point(0, 0), Point(numberOfRows - 1, numberOfColumns - 1))

  protected val diagram = new DiagramImpl(numberOfRows, numberOfColumns)

  diagram.allBoxes = findAllBoxes

  private val boxContains: Map[BoxImpl, BoxImpl] =
    (for {
      outerBox ← diagram.allBoxes
      innerBox ← diagram.allBoxes
      if outerBox != innerBox
      if outerBox.region contains innerBox.region
    } yield outerBox -> innerBox).toMap

  for {
    (box, containingBoxMap) ← boxContains.groupBy(_._2)
    containingBoxes = box :: containingBoxMap.keys.toList
    orderedBoxes = containingBoxes.sortBy(_.region.area)
    (childBox, parentBox) ← orderedBoxes.zip(orderedBoxes drop 1)
  } {
    childBox.parent = Some(parentBox)
    parentBox.childBoxes ::= childBox
  }

  for (box ← diagram.allBoxes if box.parent.isEmpty) {
    diagram.childBoxes ::= box
    box.parent = Some(diagram)
  }

  private val edges =
    diagram.allBoxes.flatMap { box ⇒
      box.rightBoundary.flatMap(followEdge(Right, _)) ++
        box.leftBoundary.flatMap(followEdge(Left, _)) ++
        box.topBoundary.flatMap(followEdge(Up, _)) ++
        box.bottomBoundary.flatMap(followEdge(Down, _))
    }

  // Each edge may occur twice (scanned from both ends), so eliminate duplicates:
  diagram.allEdges = edges.groupBy(_.points.toSet).values.toList.map(_.head)

  // Wire up edges to boxes:
  for (edge ← diagram.allEdges) {
    edge.box1.edges ::= edge
    if (edge.box1 != edge.box2)
      edge.box2.edges ::= edge
  }

  protected lazy val allEdgePoints: Set[Point] = diagram.allEdges.flatMap(_.points).toSet

  private lazy val allLabelPoints: Set[Point] =
    (for {
      edge ← diagram.allEdges
      label ← edge.label_.toList
      point ← label.points
    } yield point).toSet

  for (edge ← diagram.allEdges)
    edge.label_ = getLabel(edge)

  for (box ← diagram.allBoxes)
    box.text = collectText(box)
  diagram.text = collectText(diagram)

  protected def inDiagram(p: Point): Boolean = diagramRegion contains p

  protected def charAt(point: Point): Char = rows(point.row)(point.column)

  protected def charAtOpt(point: Point): Option[Char] =
    if (inDiagram(point)) Some(charAt(point)) else None

  def getDiagram: Diagram = diagram

  protected def isBoxEdge(point: Point) = inDiagram(point) && diagram.allBoxes.exists(_.boundaryPoints.contains(point))

  private def followEdge(direction: Direction, startPoint: Point): Option[EdgeImpl] = {
    val initialPoints = startPoint.go(direction) :: startPoint :: Nil
    if (isEdgeStart(charAt(startPoint), direction))
      followUnicodeEdge(initialPoints, direction)
    else if (!isBoxDrawingCharacter(charAt(startPoint)))
      followAsciiEdge(initialPoints, direction)
    else
      None
  }

  /**
   * Collect all the text inside a container that isn't part of another diagram element (i.e. a box, edge or label).
   */
  private def collectText(container: ContainerImpl): String = {
    val childBoxPoints = container.childBoxes.flatMap(_.region.points).toSet
    val occupiedPoints = childBoxPoints ++ allEdgePoints ++ allLabelPoints

    val sb = new StringBuilder
    val region = container.contentsRegion
    for (row ← region.topLeft.row to region.bottomRight.row) {
      for {
        column ← region.topLeft.column to region.bottomRight.column
        point = Point(row, column)
        if !occupiedPoints.contains(point)
      } sb.append(charAt(point))
      sb.append("\n")
    }
    if (sb.nonEmpty)
      sb.deleteCharAt(sb.length - 1) // Delete trailing newline
    sb.toString
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy