Skip to content

Commit

Permalink
Implemented joinX edit command
Browse files Browse the repository at this point in the history
Added new src directory: it;
KML:Mergeable:merge method now has a mergeNames parameter;
println statements replaced by log messages;
  • Loading branch information
rchillyard committed Dec 4, 2023
1 parent 6464c37 commit 692cc60
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 66 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ sampleOutput.kml
/KML_Samples_output.kml

*_out.kml

work
74 changes: 74 additions & 0 deletions src/it/scala/com/phasmidsoftware/kmldoc/KMLEditorFuncSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.phasmidsoftware.kmldoc

import cats.effect.IO
import cats.effect.unsafe.implicits.global
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should
import scala.util._

class KMLEditorFuncSpec extends AnyFlatSpec with should.Matchers {

behavior of "KMLEditor"

val placemark = "Placemark"
private val triedFilename: Success[String] = Success("src/main/resources/com/phasmidsoftware/kmldoc/placemarks.kml")

it should "processKMLs join" in {
val editor = KMLEditor(Seq(KmlEdit(KmlEdit.JOIN, 2, Element(placemark, "Salem & Lowell RR (#1)"), Some(Element(placemark, "Salem & Lowell RR (#2)"))), KmlEdit(KmlEdit.DELETE, 1, Element(placemark, "Salem & Lowell RR (#2)"), None)))
val ksi: IO[Seq[KML]] = for {
ks <- KMLCompanion.loadKML(triedFilename)
ks2 = editor.processKMLs(ks)
} yield ks2
val result = ksi.unsafeRunSync()
val feature: Feature = result.head.features.head
feature match {
case Document(fs) =>
val folder = fs.head
folder match {
case Folder(features) =>
features.size shouldBe 1
val p1 = features.head
p1 match {
case p@Placemark(g) =>
p.featureData.name.$.toString shouldBe "Salem & Lowell RR (#1)Salem & Lowell RR (#2)"
p.featureData.name.matches("Salem & Lowell RR (#1)Salem & Lowell RR (#2)") shouldBe true
val lineString = g.head
lineString match {
case LineString(_, cs) =>
cs.head.coordinates.size shouldBe 163
// TODO check the ordering of the Coordinate values.
}
}
}
}
}

it should "processKMLs joinX" in {
val editor = KMLEditor(Seq(KmlEdit(KmlEdit.JOINX, 2, Element(placemark, "Salem & Lowell RR (#1)"), Some(Element(placemark, "Salem & Lowell RR (#2)"))), KmlEdit(KmlEdit.DELETE, 1, Element(placemark, "Salem & Lowell RR (#2)"), None)))
val ksi: IO[Seq[KML]] = for {
ks <- KMLCompanion.loadKML(triedFilename)
ks2 = editor.processKMLs(ks)
} yield ks2
val result = ksi.unsafeRunSync()
val feature: Feature = result.head.features.head
feature match {
case Document(fs) =>
val folder = fs.head
folder match {
case Folder(features) =>
features.size shouldBe 1
val p1 = features.head
p1 match {
case p@Placemark(g) =>
p.featureData.name.matches("Salem & Lowell RR (#1)") shouldBe true
val lineString = g.head
lineString match {
case LineString(_, cs) =>
cs.head.coordinates.size shouldBe 163
// TODO check the ordering of the Coordinate values.
}
}
}
}
}
}
4 changes: 2 additions & 2 deletions src/main/scala/com/phasmidsoftware/core/XML.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ case class Text($: CharSequence) extends Mergeable[Text] {
* @param t the object to be merged with this.
* @return the merged value of T.
*/
def merge(t: Text): Option[Text] = ($, t.$) match {
def merge(t: Text, mergeName: Boolean = true): Option[Text] = ($, t.$) match {
case (c1: CDATA, c2: CDATA) => c1 merge c2 map (Text(_))
case _ => Some(Text($.toString + " " + t.$.toString))
}
Expand Down Expand Up @@ -85,7 +85,7 @@ case class CDATA(content: String, pre: String, post: String) extends CharSequenc
* @param t the object to be merged with this.
* @return the merged value of T.
*/
def merge(t: CDATA): Option[CDATA] = Some(CDATA(content + separator(post, t.pre) + t.content, pre, t.post))
def merge(t: CDATA, mergeName: Boolean = true): Option[CDATA] = Some(CDATA(content + separator(post, t.pre) + t.content, pre, t.post))

private def separator(a: String, b: String): String = (a + b).replaceAll("\n", "<br>")
}
Expand Down
48 changes: 23 additions & 25 deletions src/main/scala/com/phasmidsoftware/kmldoc/KML.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.phasmidsoftware.core.FP.tryNotNull
import com.phasmidsoftware.core._
import com.phasmidsoftware.kmldoc.HasFeatures.editHasFeaturesToOption
import com.phasmidsoftware.kmldoc.KMLCompanion.renderKMLToPrintStream
import com.phasmidsoftware.kmldoc.KmlEdit.editFeatures
import com.phasmidsoftware.kmldoc.KmlEdit.{JOIN, JOINX, editFeatures}
import com.phasmidsoftware.kmldoc.KmlRenderers.sequenceRendererFormatted
import com.phasmidsoftware.kmldoc.Mergeable.{mergeOptions, mergeOptionsBiased, mergeSequence, mergeStringsDelimited}
import com.phasmidsoftware.render._
Expand Down Expand Up @@ -51,7 +51,7 @@ case class KmlData(__id: Option[String]) extends Mergeable[KmlData] {
* @param k a KmlData object.
* @return the merged value of KmlData.
*/
def merge(k: KmlData): Option[KmlData] = Some(KmlData(mergeStringsDelimited(__id, k.__id)("#")))
def merge(k: KmlData, mergeName: Boolean = true): Option[KmlData] = Some(KmlData(mergeStringsDelimited(__id, k.__id)("#")))
}

/**
Expand Down Expand Up @@ -125,10 +125,10 @@ case class FeatureData(name: Text, maybeDescription: Option[Text], maybeStyleUrl
* @param f a FeatureData object.
* @return the merged value of FeatureData.
*/
def merge(f: FeatureData): Option[FeatureData] = {
def merge(f: FeatureData, mergeName: Boolean = true): Option[FeatureData] = {
// TODO warn if styles are not the same.
for {
n <- name merge f.name
n <- if (mergeName) name merge f.name else Some(name)
d = mergeOptions(maybeDescription, f.maybeDescription)((t1, t2) => t1 merge t2)
z <- kmlData merge f.kmlData
} yield FeatureData(n, d, maybeStyleUrl, maybeOpen, maybeVisibility, StyleSelectors, abstractView)(z) // TODO: not all fields are properly merged
Expand Down Expand Up @@ -190,7 +190,7 @@ trait Geometry extends KmlObject with Mergeable[Geometry] {
* @param t the object to be merged with this.
* @return the merged value of T.
*/
def merge(t: Geometry): Option[Geometry] = throw KmlException(s"merge not implemented for this class: ${t.getClass}")
def merge(t: Geometry, mergeName: Boolean = true): Option[Geometry] = throw KmlException(s"merge not implemented for this class: ${t.getClass}")
}

/**
Expand All @@ -211,7 +211,7 @@ object Geometry extends Extractors with Renderers {
* @param kmlData source of properties.
*/
case class GeometryData(maybeExtrude: Option[Extrude], maybeAltitudeMode: Option[AltitudeMode])(val kmlData: KmlData) extends Mergeable[GeometryData] {
def merge(g: GeometryData): Option[GeometryData] =
def merge(g: GeometryData, mergeName: Boolean = true): Option[GeometryData] =
for {
k <- kmlData merge g.kmlData
} yield GeometryData(mergeOptionsBiased(maybeExtrude, g.maybeExtrude), mergeOptionsBiased(maybeAltitudeMode, g.maybeAltitudeMode))(k)
Expand Down Expand Up @@ -405,14 +405,14 @@ case class Placemark(Geometry: Seq[Geometry])(val featureData: FeatureData) exte
* @param t the object to be merged with this.
* @return the merged value of T.
*/
def merge(t: Placemark): Option[Placemark] = {
println(s"joinPlacemarks: $name, ${t.name}")
def merge(t: Placemark, mergeName: Boolean = true): Option[Placemark] = {
logger.info(s"joinPlacemarks: $name, ${t.name} with mergeName=$mergeName")
val gps: Seq[Geometry] = this.Geometry
val gqs = t.Geometry
val los: Seq[Option[Geometry]] = for (gp <- gps; gq <- gqs) yield gp.merge(gq)
val z: Seq[Geometry] = los filter (_.isDefined) map (_.get)
for {
xx <- this.featureData merge t.featureData
xx <- featureData.merge(t.featureData, mergeName)
} yield Placemark(z)(xx)
}

Expand Down Expand Up @@ -441,7 +441,7 @@ case class Placemark(Geometry: Seq[Geometry])(val featureData: FeatureData) exte
private def editMatching1(e: KmlEdit) = (name, e) match {
case (name, KmlEdit(KmlEdit.DELETE, _, Element(_, nameToMatch), None))
if name.matches(nameToMatch) =>
System.err.println(s"delete: $nameToMatch") // TODO generate a log message
logger.info(s"delete: $nameToMatch")
Some(None)
case (_, KmlEdit(KmlEdit.DELETE, _, _, _)) =>
Some(Some(this))
Expand All @@ -457,10 +457,10 @@ case class Placemark(Geometry: Seq[Geometry])(val featureData: FeatureData) exte
* @return an optional optional Feature.
*/
private def editMatchingPlacemark2(e: KmlEdit, fs: Seq[Feature]) =
(name, e) match {
case (name, KmlEdit(KmlEdit.JOIN, _, Element("Placemark", nameToMatch1), Some(Element("Placemark", nameToMatch2))))
e match {
case KmlEdit(command@(JOIN | JOINX), _, Element("Placemark", nameToMatch1), Some(Element("Placemark", nameToMatch2)))
if name.matches(nameToMatch1) =>
Some(joinMatchedPlacemarks(fs, nameToMatch2))
Some(joinMatchedPlacemarks(fs, nameToMatch2, command == JOIN))
case _ =>
None
}
Expand All @@ -472,17 +472,15 @@ case class Placemark(Geometry: Seq[Geometry])(val featureData: FeatureData) exte
* @param nameToMatch the name of the feature to be joined, as defined by the edit.
* @return an optional Feature which, if defined, is the new Placemark to be used instead of <code>p</code>.
*/
private def joinMatchedPlacemarks(fs: Seq[Feature], nameToMatch: String) = {
System.err.println(s"join: $name with $nameToMatch") // TODO generate a log message
val zz = for (f <- fs if f != this) yield joinMatchingPlacemarks(nameToMatch, f)
private def joinMatchedPlacemarks(fs: Seq[Feature], nameToMatch: String, mergeName: Boolean) = {
val zz = for (f <- fs if f != this) yield joinMatchingPlacemarks(nameToMatch, f, mergeName)
for (z <- zz.find(_.isDefined); q <- z) yield q
}

private def joinMatchingPlacemarks(name: String, feature: Feature) =
feature match {
case q: Placemark if q.name.matches(name) => this merge q
case _ => None
}
private def joinMatchingPlacemarks(name: String, feature: Feature, mergeName: Boolean) = feature match {
case q: Placemark if q.name.matches(name) => merge(q, mergeName)
case _ => None
}
}

/**
Expand Down Expand Up @@ -559,7 +557,7 @@ case class LineString(tessellate: Tessellate, coordinates: Seq[Coordinates])(val

import Coordinates.empty

override def merge(g: Geometry): Option[Geometry] = g match {
override def merge(g: Geometry, mergeName: Boolean = true): Option[Geometry] = g match {
case l@LineString(_, _) =>
for {
t <- tessellate merge l.tessellate
Expand Down Expand Up @@ -900,7 +898,7 @@ object LabelStyle extends Extractors with Renderers {
* @param $ the value.
*/
case class Tessellate($: CharSequence) extends Mergeable[Tessellate] {
def merge(t: Tessellate): Option[Tessellate] = ($, t.$) match {
def merge(t: Tessellate, mergeName: Boolean = true): Option[Tessellate] = ($, t.$) match {
case (a, b) if a == b => Some(Tessellate(a))
case _ => None
}
Expand Down Expand Up @@ -982,7 +980,7 @@ case class Coordinates(coordinates: Seq[Coordinate]) extends Mergeable[Coordinat

def gap(other: Coordinates): Option[Double] = gapInternal(coordinates, other.coordinates)

def merge(other: Coordinates): Option[Coordinates] = {
def merge(other: Coordinates, mergeName: Boolean = true): Option[Coordinates] = {
val xo = vector
val yo: Option[Cartesian] = other.vector
val zo: Option[Double] = for (x <- xo; y <- yo) yield x dotProduct y
Expand Down Expand Up @@ -1562,7 +1560,7 @@ trait Mergeable[T] {
* @param t the object to be merged with this.
* @return the merged value of T.
*/
def merge(t: T): Option[T]
def merge(t: T, mergeName: Boolean = true): Option[T]
}

/**
Expand Down
16 changes: 14 additions & 2 deletions src/main/scala/com/phasmidsoftware/kmldoc/KMLEdit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ case class KmlEdit(command: String, operands: Int, op1: Element, maybeOp2: Optio
object KmlEdit {

def operands(command: String): Int = command match {
case KmlEdit.JOIN => 2
case KmlEdit.DELETE => 1
case JOIN | JOINX => 2
case DELETE => 1
case _ => 0
}

Expand Down Expand Up @@ -61,7 +61,19 @@ object KmlEdit {
*/
def parseLines(ws: Iterator[String]): IO[Seq[KmlEdit]] = (for (w <- ws) yield parse(w)).toSeq.sequence

/**
* Join two elements (typically using the merge method of Mergeable KML objects).
*/
val JOIN = "join"

/**
* like JOIN but retains the first name (i.e. it excludes the second name).
*/
val JOINX = "joinX"

/**
* delete an element.
*/
val DELETE = "delete"
}

Expand Down
11 changes: 7 additions & 4 deletions src/main/scala/com/phasmidsoftware/kmldoc/KMLEditor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.phasmidsoftware.kmldoc.KMLCompanion.renderKMLs
import com.phasmidsoftware.kmldoc.KMLEditor.{addExtension, write}
import com.phasmidsoftware.render.FormatXML
import java.io.{BufferedWriter, File, FileWriter, Writer}
import org.slf4j.{Logger, LoggerFactory}
import scala.annotation.tailrec
import scala.io.Source
import scala.util._
Expand All @@ -19,7 +20,7 @@ import scala.util._
*/
case class KMLEditor(edits: Seq[KmlEdit]) {

System.err.println(s"KMLEditor: ${edits.mkString}") // TODO generate log message
KMLEditor.logger.info(s"KMLEditor: ${edits.mkString}")

/**
* Method to process the file defined by baseFilename by parsing it, editing it, and writing it out.
Expand All @@ -34,8 +35,8 @@ case class KMLEditor(edits: Seq[KmlEdit]) {
val inputFile = addExtension(baseFilename, kml)
val outExt = "_out" + kml
val outputFile = addExtension(baseFilename, outExt)
inputFile foreach (f => System.err.println(s"KMLEditor.process from $f")) // TODO generate a log message
outputFile foreach (f => System.err.println(s"KMLEditor.process to $f")) // TODO generate a log message
inputFile foreach (f => KMLEditor.logger.info(s"KMLEditor.process from $f"))
outputFile foreach (f => KMLEditor.logger.info(s"KMLEditor.process to $f"))
processFromTo(inputFile, outputFile)
}

Expand All @@ -50,7 +51,7 @@ case class KMLEditor(edits: Seq[KmlEdit]) {
private def processFromTo(inputFile: Try[String], outputFile: Try[String]): IO[Unit] = {
val qsi: IO[Seq[Writer]] = for {
w <- IO.fromTry(outputFile)
_ = println(w)
// _ = println(w)
f <- IO(new File(w))
bW = new BufferedWriter(new FileWriter(f, false))
ks <- KMLCompanion.loadKML(inputFile)
Expand Down Expand Up @@ -89,6 +90,8 @@ case class KMLEditor(edits: Seq[KmlEdit]) {
}

object KMLEditor {
val logger: Logger = LoggerFactory.getLogger(KMLEditor.getClass)

/**
* Method to construct a KMLEditor from a filename.
*
Expand Down
33 changes: 0 additions & 33 deletions src/test/scala/com/phasmidsoftware/kmldoc/KMLEditorSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,6 @@ class KMLEditorSpec extends AnyFlatSpec with should.Matchers {
behavior of "KMLEditor"

val placemark = "Placemark"
private val triedFilename: Success[String] = Success("src/main/resources/com/phasmidsoftware/kmldoc/placemarks.kml")

it should "processKMLs" in {
val editor = KMLEditor(Seq(KmlEdit(KmlEdit.JOIN, 2, Element(placemark, "Salem & Lowell RR (#1)"), Some(Element(placemark, "Salem & Lowell RR (#2)"))), KmlEdit(KmlEdit.DELETE, 1, Element(placemark, "Salem & Lowell RR (#2)"), None)))
val ksi: IO[Seq[KML]] = for {
ks <- KMLCompanion.loadKML(triedFilename)
ks2 = editor.processKMLs(ks)
} yield ks2
val result = ksi.unsafeRunSync()
val feature: Feature = result.head.features.head
feature match {
case Document(fs) =>
val folder = fs.head
folder match {
case Folder(features) =>
features.size shouldBe 1
val p1 = features.head
p1 match {
case Placemark(g) =>
val lineString = g.head
lineString match {
case LineString(_, cs) =>
cs.head.coordinates.size shouldBe 163
// TODO check the ordering of the Coordinate values.
}
}
}
}
}

it should "edits" in {

}

it should "parse" in {
val result: IO[KMLEditor] = KMLEditor.parse(Success("src/main/resources/com/phasmidsoftware/kmldoc/sampleEdits.txt"))
Expand Down

0 comments on commit 692cc60

Please sign in to comment.