def fromCrossword()

in common/app/model/CrosswordData.scala [91:165]


  def fromCrossword(crossword: Crossword, content: contentapi.Content): CrosswordData = {
    // For entry groups, all separator locations for entries within the
    // group are incorrectly stored on the first group entry. We normalize
    // the data to store the separator locations on their corresponding entries.

    val shipSolutions = crossword.dateSolutionAvailable.map(_.toJoda.isBeforeNow).getOrElse(crossword.solutionAvailable)

    val entries = crossword.entries.collect {
      case entry if !shipSolutions => Entry.fromCrosswordEntry(entry.copy(solution = None))
      case entry                   => Entry.fromCrosswordEntry(entry)
    }

    val entryGroups = entries
      .groupBy(_.group)
      // Sort using the group ordering
      .map { case (orderedGroupEntryIds, groupEntries) =>
        (orderedGroupEntryIds, orderedGroupEntryIds.flatMap(id => groupEntries.find(_.id == id)))
      }

    val newEntries = entryGroups
      .map { case (_, groupEntries) =>
        val maybeSeparatorLocations: Option[Map[String, Seq[Int]]] =
          groupEntries
            .find(_.separatorLocations.exists(_.nonEmpty))
            .flatMap(_.separatorLocations)

        val bounds: Map[(Int, Int), Entry] =
          groupEntries
            .foldLeft((0, Map.empty[(Int, Int), Entry])) { case ((upperBound, m), entry) =>
              val newLowerBound = upperBound
              val newUpperBound = upperBound + entry.length
              (newUpperBound, m + ((newLowerBound, newUpperBound) -> entry))
            }
            ._2

        val newGroupEntries: Seq[Entry] = bounds.map { case ((lowerBound, upperBound), entry) =>
          maybeSeparatorLocations
            .map { separatorLocations =>
              val newSeparatorLocations: Map[String, Seq[Int]] = separatorLocations.map { case (separator, locations) =>
                val newLocations =
                  locations
                    .filter(location => location > lowerBound && location <= upperBound)
                    .map(location => location - lowerBound)
                (separator, newLocations)
              }
              entry.copy(separatorLocations = Some(newSeparatorLocations))
            }
            .getOrElse(entry)
        }.toList

        newGroupEntries
      }
      .toList
      .flatten

    // Revert back to the original order
    val sortedNewEntries = entries.flatMap(entry => newEntries.find(_.id == entry.id))
    val crosswordType = CamelCase.toHyphenated(crossword.`type`.name)

    CrosswordData(
      s"crosswords/$crosswordType/${crossword.number.toString}",
      crossword.number,
      crossword.name,
      creator = for (creator <- crossword.creator) yield CrosswordCreator(creator.name, creator.webUrl),
      crossword.date.toJoda,
      content.webPublicationDate.fold(crossword.date.toJoda)(_.toJoda),
      sortedNewEntries.toSeq,
      solutionAvailable = shipSolutions,
      crossword.dateSolutionAvailable.map(_.toJoda),
      CrosswordDimensions(crossword.dimensions.cols, crossword.dimensions.rows),
      crosswordType,
      crossword.pdf,
      crossword.instructions,
    )
  }