diff --git a/.gitignore b/.gitignore
index 1c050d7..7bfa204 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,5 @@ sampleOutput.kml
/KML_Samples_output.kml
*_out.kml
+
+work
diff --git a/src/it/scala/com/phasmidsoftware/kmldoc/KMLEditorFuncSpec.scala b/src/it/scala/com/phasmidsoftware/kmldoc/KMLEditorFuncSpec.scala
new file mode 100644
index 0000000..8d25e92
--- /dev/null
+++ b/src/it/scala/com/phasmidsoftware/kmldoc/KMLEditorFuncSpec.scala
@@ -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.
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/scala/com/phasmidsoftware/core/XML.scala b/src/main/scala/com/phasmidsoftware/core/XML.scala
index da3921c..8033f61 100644
--- a/src/main/scala/com/phasmidsoftware/core/XML.scala
+++ b/src/main/scala/com/phasmidsoftware/core/XML.scala
@@ -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))
}
@@ -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", "
")
}
diff --git a/src/main/scala/com/phasmidsoftware/kmldoc/KML.scala b/src/main/scala/com/phasmidsoftware/kmldoc/KML.scala
index cdff36d..0bab074 100644
--- a/src/main/scala/com/phasmidsoftware/kmldoc/KML.scala
+++ b/src/main/scala/com/phasmidsoftware/kmldoc/KML.scala
@@ -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._
@@ -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)("#")))
}
/**
@@ -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
@@ -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}")
}
/**
@@ -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)
@@ -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)
}
@@ -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))
@@ -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
}
@@ -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 p
.
*/
- 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
+ }
}
/**
@@ -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
@@ -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
}
@@ -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
@@ -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]
}
/**
diff --git a/src/main/scala/com/phasmidsoftware/kmldoc/KMLEdit.scala b/src/main/scala/com/phasmidsoftware/kmldoc/KMLEdit.scala
index 102805d..d4f9cb6 100644
--- a/src/main/scala/com/phasmidsoftware/kmldoc/KMLEdit.scala
+++ b/src/main/scala/com/phasmidsoftware/kmldoc/KMLEdit.scala
@@ -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
}
@@ -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"
}
diff --git a/src/main/scala/com/phasmidsoftware/kmldoc/KMLEditor.scala b/src/main/scala/com/phasmidsoftware/kmldoc/KMLEditor.scala
index 91ac45a..b9ee31b 100644
--- a/src/main/scala/com/phasmidsoftware/kmldoc/KMLEditor.scala
+++ b/src/main/scala/com/phasmidsoftware/kmldoc/KMLEditor.scala
@@ -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._
@@ -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.
@@ -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)
}
@@ -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)
@@ -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.
*
diff --git a/src/test/scala/com/phasmidsoftware/kmldoc/KMLEditorSpec.scala b/src/test/scala/com/phasmidsoftware/kmldoc/KMLEditorSpec.scala
index 90ff14a..8dd9f99 100644
--- a/src/test/scala/com/phasmidsoftware/kmldoc/KMLEditorSpec.scala
+++ b/src/test/scala/com/phasmidsoftware/kmldoc/KMLEditorSpec.scala
@@ -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"))