diff --git a/.travis.yml b/.travis.yml
index d8b54e5..31c4a08 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,6 +16,7 @@ env:
script:
- sbt "++$TRAVIS_SCALA_VERSION" "nitf${version}/test"
+ - sbt "++$TRAVIS_SCALA_VERSION" "nitf${version}/doc"
# These directories are cached to S3 at the end of the build
cache:
@@ -24,6 +25,6 @@ cache:
- $HOME/.sbt
before_cache:
- # Cleanup the cached directories to avoid unnecessary cache updates
+ # Clean up the cached directories to avoid unnecessary cache updates
- find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete
- find $HOME/.sbt -name "*.lock" -print -delete
diff --git a/project/Build.scala b/project/Build.scala
index 9766add..3de52b9 100644
--- a/project/Build.scala
+++ b/project/Build.scala
@@ -57,7 +57,7 @@ object BuildSettings {
name := Metadata.projectName,
crossScalaVersions := Dependencies.scalaVersions,
scalaVersion := Dependencies.scalaVersions.min,
- scalacOptions += "-target:jvm-1.8",
+ scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-target:jvm-1.8"),
dependencyCheckFailBuildOnCVSS := 4
)
diff --git a/project/Dependencies.scala b/project/Dependencies.scala
index a4ed278..60206db 100644
--- a/project/Dependencies.scala
+++ b/project/Dependencies.scala
@@ -14,7 +14,6 @@ object Dependencies {
val scalaTestVersion = "3.0.5"
val scalaTest = "org.scalatest" %% "scalatest" % scalaTestVersion
- val scalactic = "org.scalactic" %% "scalactic" % scalaTestVersion
val xmlDiff = "com.github.andyglow" %% "scala-xml-diff" % "2.0.3"
diff --git a/src/main/scala/Builders.scala b/src/main/scala/Builders.scala
index 7ebfc6c..38d1074 100644
--- a/src/main/scala/Builders.scala
+++ b/src/main/scala/Builders.scala
@@ -25,6 +25,7 @@ object `package` {
type NoteType = TypeType2
type PublicationType = TypeType
type TaglineType = TypeType3
+ type TitleType = Type
val BareNitfNamespace: NamespaceBinding = toScope(None -> defaultScope.uri)
@@ -42,6 +43,31 @@ trait Builder[T] {
override def toString: String = build.toString
}
+trait BlockContentBuilder {
+ def withNote(x: Note): this.type = withBlockContent(x)
+ def withFootnote(x: Fn): this.type = withBlockContent(x)
+ def withMedia(x: Media): this.type = withBlockContent(x)
+ def withParagraph(x: P): this.type = withBlockContent(x)
+ def withTable(x: Table): this.type = withBlockContent(x)
+ def withBlockQuote(x: Bq): this.type = withBlockContent(x)
+ def withOrderedList(x: Ol): this.type = withBlockContent(x)
+ def withPreformatted(x: Pre): this.type = withBlockContent(x)
+ def withUnorderedList(x: Ul): this.type = withBlockContent(x)
+ def withDefinitionList(x: Dl): this.type = withBlockContent(x)
+ def withHorizontalRule(x: Hr): this.type = withBlockContent(x)
+ def withNitfTable(x: NitfTable): this.type = withBlockContent(x)
+ def withSubordinateHeadline(x: Hl2): this.type = withBlockContent(x)
+
+ def withTextParagraph(x: String, markAsSummary: Boolean = true): this.type = {
+ var paragraphBuilder = new ParagraphBuilder().withText(x)
+ if (markAsSummary) paragraphBuilder = paragraphBuilder.asSummary
+ withParagraph(paragraphBuilder.build)
+ }
+
+ protected def withBlockContent[T <: BlockContentOption : CanWriteXML](x: T): this.type = withContent(dataRecord(x))
+ protected def withContent(x: DataRecord[_]): this.type
+}
+
trait EnrichedTextBuilder {
def withAnchor(x: A): this.type = withEnrichedText(x)
def withChron(x: Chron): this.type = withEnrichedText(x)
@@ -62,26 +88,66 @@ trait EnrichedTextBuilder {
def withObjectTitle(x: ObjectTitle): this.type = withEnrichedText(x)
def withPronunciation(x: Pronounce): this.type = withEnrichedText(x)
def withVirtualLocation(x: Virtloc): this.type = withEnrichedText(x)
- def withXml(x: NodeSeq): this.type = withContent(dataRecord(x))
- def withText(x: String): this.type = withContent(dataRecord(x))
protected def withEnrichedText[T <: EnrichedTextOption : CanWriteXML](x: T): this.type = withContent(dataRecord(x))
protected def withContent(x: DataRecord[_]): this.type
}
+trait MixedContentBuilder {
+ def withText(x: String): this.type = withContent(dataRecord(x))
+
+ /** Appends the given XML to the model object.
+ * Note that this method is _not_ type-safe!
+ * __No__ validation is performed to verify that the XML matches the expected schema..
+ *
+ * @deprecated Use multiple invocations of other methods to construct the data for a type-safe approach
+ */
+ @deprecated("Use multiple invocations of other methods for a type-safe approach", since = "3.x.3")
+ def withXml(x: NodeSeq): this.type = withContent(dataRecord(x))
+
+ protected def withContent(x: DataRecord[_]): this.type
+}
+
+/** An extension point to enable writing custom XML to NITF model objects that support extensions.
+ * This feature is supported by NITF 3.6 where the model objects contain a special field called ''any'', which acts as
+ * an extension point for provider-defined properties from other namespaces.
+ *
+ * This is an example of how it can be used (with versions 3.6.x only):
+ * {{{
+ * import com.gu.nitf.model.builders._
+ * import com.gu.nitf.scalaxb._
+ * import scalaxb._
+ *
+ * val builder = new NitfBuilder() with AnyExtensionsBuilder {
+ * protected override def withAny(x: DataRecord[_]) = { build = build.copy(any = build.any :+ x); this }
+ * }
+ *
+ * val nitf = builder.withXml().build
+ * val xml = scalaxb.toXML(nitf, None, None, toScope(Some("my") -> "http://www.example.com/my-extension"))
+ * }}}
+ */
+trait AnyContentBuilder { this: Builder[_ <: { def any: Seq[scalaxb.DataRecord[Any]] }] =>
+ def withXml(x: NodeSeq): this.type = withAny(dataRecord(x))
+ protected def withAny(x: DataRecord[_]): this.type
+}
+
class NitfBuilder(var build: Nitf = Nitf(body = Body())) extends Builder[Nitf] {
- def withHead(x: Head): this.type = { build = build.copy(head = Option(x)); this }
def withBody(x: Body): this.type = { build = build.copy(body = x); this }
+ def withHead(x: Head): this.type = { build = build.copy(head = Option(x)); this }
def withUno(x: String): this.type = { build = build.copy(uno = Option(x)); this }
}
class HeadBuilder(var build: Head = Head()) extends Builder[Head] {
- def withTitle(x: String): this.type = { build = build.copy(title = Option(x).map(t => Title(Seq(dataRecord(t))))); this }
+ def withTitle(x: String, titleType: Option[TitleType] = None): this.type = {
+ build = build.copy(title = Option(x).map(t => Title(Seq(dataRecord(t)), typeValue = titleType)))
+ this
+ }
def withDocData(x: Docdata): this.type = { build = build.copy(docdata = Option(x)); this }
def withPublicationData(x: Pubdata): this.type = { build = build.copy(pubdata = build.pubdata :+ x); this }
}
class DocDataBuilder(var build: Docdata = Docdata()) extends Builder[Docdata] {
+ def withDocId(x: String): this.type = withDocId(DocId(idString = Option(x)))
def withDocId(x: DocId): this.type = withDocDataOption(x)
def withCopyright(x: DocCopyright): this.type = withDocDataOption(x)
def withIssueDate(x: LocalDate): this.type = withDocDataOption(DateIssue(norm = optionalString(x)))
@@ -91,7 +157,7 @@ class DocDataBuilder(var build: Docdata = Docdata()) extends Builder[Docdata] {
build = build.copy(managementStatus = optionalString(x))
this
}
- private def withDocDataOption[T <: DocdataOption : CanWriteXML](x: T): this.type = {
+ protected def withDocDataOption[T <: DocdataOption : CanWriteXML](x: T): this.type = {
build = build.copy(docdataoption = build.docdataoption :+ dataRecord(x))
this
}
@@ -115,17 +181,12 @@ class PublicationDataBuilder(var build: Pubdata = Pubdata()) extends Builder[Pub
class BodyBuilder(var build: Body = Body()) extends Builder[Body] {
def withHead(x: BodyHead): this.type = { build = build.copy(bodyHead = Option(x)); this }
def withContent(x: BodyContent): this.type = { build = build.copy(bodyContent = build.bodyContent :+ x); this }
+ def withEnd(x: BodyEnd): this.type = { build = build.copy(bodyEnd = Option(x)); this }
}
class BodyHeadBuilder(var build: BodyHead = BodyHead()) extends Builder[BodyHead] {
def withByline(x: String): this.type = withByline(Byline(Seq(dataRecord(x))))
def withHeadline(x: String): this.type = withHeadline(new HeadlineBuilder().withPrimaryHeadline(x))
- def withAbstract(x: NodeSeq): this.type = withAbstract(Abstract(Seq(dataRecord(x))))
- def withAbstract(x: String, markAsSummary: Boolean = true): this.type = {
- var paragraphBuilder = new ParagraphBuilder().withText(x)
- if (markAsSummary) paragraphBuilder = paragraphBuilder.asSummary
- withAbstract(Abstract(Seq(dataRecord(paragraphBuilder.build))))
- }
def withRights(x: Rights): this.type = { build = build.copy(rights = Option(x)); this }
def withSeries(x: Series): this.type = { build = build.copy(series = Option(x)); this }
@@ -141,6 +202,13 @@ class BodyHeadBuilder(var build: BodyHead = BodyHead()) extends Builder[BodyHead
}
}
+class AbstractBuilder(var build: Abstract = Abstract()) extends Builder[Abstract] with BlockContentBuilder {
+ protected def withContent(x: DataRecord[_]): this.type = {
+ build = build.copy(abstractoption = build.abstractoption :+ x)
+ this
+ }
+}
+
class HeadlineBuilder(var build: Hedline = Hedline(Hl1())) extends Builder[Hedline] {
def withPrimaryHeadline(x: Hl1): this.type = { build = build.copy(hl1 = x); this }
def withPrimaryHeadline(x: String): this.type = { withPrimaryHeadline(new PrimaryHeadlineBuilder(build.hl1).withText(x)) }
@@ -148,30 +216,10 @@ class HeadlineBuilder(var build: Hedline = Hedline(Hl1())) extends Builder[Hedli
def withSubordinateHeadline(x: String): this.type = { withSubordinateHeadline(Hl2(Seq(dataRecord(x)))); this }
}
-class PrimaryHeadlineBuilder(var build: Hl1 = Hl1()) extends Builder[Hl1] with EnrichedTextBuilder {
+class PrimaryHeadlineBuilder(var build: Hl1 = Hl1()) extends Builder[Hl1] with EnrichedTextBuilder with MixedContentBuilder {
protected def withContent(x: DataRecord[_]): this.type = { build = build.copy(mixed = build.mixed :+ x); this }
}
-trait BlockContentBuilder {
- def withNote(x: Note): this.type = withBlockContent(x)
- def withFootnote(x: Fn): this.type = withBlockContent(x)
- def withMedia(x: Media): this.type = withBlockContent(x)
- def withParagraph(x: P): this.type = withBlockContent(x)
- def withTable(x: Table): this.type = withBlockContent(x)
- def withBlockQuote(x: Bq): this.type = withBlockContent(x)
- def withOrderedList(x: Ol): this.type = withBlockContent(x)
- def withPreformatted(x: Pre): this.type = withBlockContent(x)
- def withUnorderedList(x: Ul): this.type = withBlockContent(x)
- def withDefinitionList(x: Dl): this.type = withBlockContent(x)
- def withHorizontalRule(x: Hr): this.type = withBlockContent(x)
- def withNitfTable(x: NitfTable): this.type = withBlockContent(x)
- def withSubordinateHeadline(x: Hl2): this.type = withBlockContent(x)
- def withXml(x: NodeSeq): this.type = withContent(dataRecord(x))
-
- protected def withBlockContent[T <: BlockContentOption : CanWriteXML](x: T): this.type = withContent(dataRecord(x))
- protected def withContent(x: DataRecord[_]): this.type
-}
-
class BodyContentBuilder(var build: BodyContent = BodyContent()) extends Builder[BodyContent] with BlockContentBuilder {
def withBlock(x: Block): this.type = withContent(dataRecord(x))
protected override def withContent(x: DataRecord[_]): this.type = {
@@ -208,6 +256,11 @@ class MediaBuilder(var build: Media) extends Builder[Media] {
}
}
+class MediaCaptionBuilder(var build: MediaCaption = MediaCaption())
+ extends Builder[MediaCaption] with BlockContentBuilder with EnrichedTextBuilder with MixedContentBuilder {
+ protected override def withContent(x: DataRecord[_]): this.type = { build = build.copy(build.mixed :+ x); this }
+}
+
class MediaMetadataBuilder(var build: MediaMetadata) extends Builder[MediaMetadata] {
def this(name: String) = this(MediaMetadata(name = name))
def this(name: String, value: String) = this(MediaMetadata(name = name, valueAttribute = Option(value)))
@@ -216,7 +269,7 @@ class MediaMetadataBuilder(var build: MediaMetadata) extends Builder[MediaMetada
def withValue(x: String): this.type = { build = build.copy(valueAttribute = Option(x)); this }
}
-class MediaReferenceBuilder(var build: MediaReference = MediaReference()) extends Builder[MediaReference] {
+class MediaReferenceBuilder(var build: MediaReference = MediaReference()) extends Builder[MediaReference] with MixedContentBuilder {
def asNoFlow: this.type = { build = build.copy(noflow = Some(NoflowValue)); this }
def withSource(x: URI): this.type = withSource(x.toString)
def withSource(x: String): this.type = { build = build.copy(source = Option(x)); this }
@@ -232,9 +285,11 @@ class MediaReferenceBuilder(var build: MediaReference = MediaReference()) extend
def withSourceCredit(x: String): this.type = { build = build.copy(sourceCredit = Option(x)); this }
def withTimeUnitOfMeasure(x: String): this.type = { build = build.copy(timeUnitOfMeasure = Option(x)); this }
def withTimeLength(x: Int): this.type = { build = build.copy(time = Option(x.toString)); this }
+
+ protected override def withContent(x: DataRecord[_]): this.type = { build = build.copy(build.mixed :+ x); this }
}
-class ParagraphBuilder(var build: P = P()) extends Builder[P] with EnrichedTextBuilder {
+class ParagraphBuilder(var build: P = P()) extends Builder[P] with EnrichedTextBuilder with MixedContentBuilder {
def asLead: this.type = { build = build.copy(lede = Option("true")); this }
def asSummary: this.type = { build = build.copy(summary = Option("true")); this }
def asOptional: this.type = { build = build.copy(optionalText = Option("true")); this }
diff --git a/src/test/scala/BuildersSpec.scala b/src/test/scala/BuildersSpec.scala
index 2e0045a..3a2f15f 100644
--- a/src/test/scala/BuildersSpec.scala
+++ b/src/test/scala/BuildersSpec.scala
@@ -32,7 +32,7 @@ class BuildersSpec extends FunSpec {
.withHead(new BodyHeadBuilder()
.withHeadline("News Article")
.withByline("It took a lot of work to get there")
- .withAbstract(
It wasn't easy, but they never gave up!
)
+ .withAbstract(new AbstractBuilder().withTextParagraph("It wasn't easy, but they never gave up!"))
)
.withContent(new BodyContentBuilder()
.withParagraph(new ParagraphBuilder().withText("It was done, really!"))
diff --git a/src/test/scala/TwoWaySpec.scala b/src/test/scala/TwoWaySpec.scala
index 2cd3f13..93e3785 100644
--- a/src/test/scala/TwoWaySpec.scala
+++ b/src/test/scala/TwoWaySpec.scala
@@ -34,7 +34,7 @@ class TwoWaySpec extends FunSpec {
import TwoWaySpec._
describe("the parser") {
- it("should parse and regenerate a sample file") {
+ it(s"should parse and regenerate a sample file (v$schemaVersion)") {
val example = XML.load(resource(s"nitf-example-$schemaVersion.xml"))
val schemaLocation = example.attribute(namespaces("xsi"), "schemaLocation").get.head.text