MetaData and MetaDataConverter
Methods to convert Paragraph to MetaData. A Paragraph is MetaData if all
lines can be converted to key/value strings.
A key value string is a line that has two text sections divided by a colon.
key : value
Spaces around the key will be trimmed.
A key value can have a trailing line appended to the value, only if the previous line ends with two spaces and then a newline.
MetaData
MetaData is really just a Map of strings. But, we want to return it in the
BlockSeq, so it's a special kind of Block.
The values of the map shoudn't contain the separating newline.
There is no XML representation of MetaData. Conversion to header elements is
left to your program to interpret.
// In com/tristanhunt/knockoff/extra/MetaData.scala
package com.tristanhunt.knockoff.extra
// See the MetaData imports
class MetaData( val data : Map[ String, String ], val position : Position )
extends SimpleBlock {
val span : SpanSeq = new Text( markdown )
/** This should be an empty element. */
val xml : Node = new Group( new NodeBuffer )
val markdown : String =
data.map{ case ((k,v)) => k + ": " + v }.mkString("\n")
// See the MetaData toString, equals, hashCode methods
}
// The MetaData imports
import scala.util.parsing.input.Position
import scala.xml.{ Group, Node, NodeBuffer }
// The MetaData toString, equals, hashCode methods
override def toString : String = "MetaData(" +
Seq( data, position ).mkString("/") +
")"
override def equals( rhs : Any ) : Boolean = rhs match {
case oth : MetaData =>
( oth.canEqual( this ) ) &&
( data sameElements oth.data ) &&
( position == oth.position )
case _ => false
}
def canEqual( rhs : MetaData ) : Boolean =
rhs.isInstanceOf[ MetaData ]
override def hashCode : Int = 41 * ( data.hashCode + 41 * position.hashCode )
MetaDataConverter
// In com/tristanhunt/knockoff/extra/MetaDataConverter.scala
package com.tristanhunt.knockoff.extra
// See the MetaDataConverter imports
trait MetaDataConverter {
/**
@param para The paragraph to be converted. We use the trimmed markdown
value to determine the data.
@return Some metadata equivalent of the paragraph. Or, None.
*/
def toMetaData( para : Paragraph ) : Option[ MetaData ] =
parseLine( para.markdown.trim.split("\n"), new ListBuffer )
.map( new MetaData( _, para.position ) )
/**
Creates the string map that becomes the main data for MetaData.
@param in Each line of markdown content, minus the trailing newlines.
@param out The current "chunks" of metadata. Each chunk can contain multiple
lines.
@return The parsed Metadata when every line can be chunked, or None.
*/
private def parseLine( in : Seq[ String ], out : ListBuffer[ String ] )
: Option[ Map[ String, String ] ] = {
if ( in.isEmpty ) return Some( toMetaDataMap( out ) )
in.first.indexOf(':') match {
case -1 =>
if ( out.isEmpty || ! out.last.endsWith(" ") ) return None
else {
val newChunk = out.last + "\n" + in.first
out.trimEnd(1)
out += newChunk
}
case idx => out += in.first
}
parseLine( in.drop(1), out )
}
private def toMetaDataMap( out : Seq[ String ] ) : Map[ String, String ] =
Map( out.map { chunk =>
val idx = chunk.indexOf(':')
( chunk.substring(0, idx).trim, chunk.substring(idx + 1) )
} : _* )
}
// The MetaDataConverter imports
import scala.collection.mutable.ListBuffer
MetaDataConverterSpec
// In test com/tristanhunt/knockoff/extra/MetaDataConverterSpec.scala
package com.tristanhunt.knockoff.extra
// See the MetaDataConverterSpec imports
class MetaDataConverterSpec extends Spec with ShouldMatchers with Wholesaler {
describe("MetaDataConverter") {
it("should convert a Paragraph to MetaData") {
val content = "key 1 : value\n" +
"key2:value\n" +
"key 3 : long \n" +
" value\n" +
"key4:\n" +
":oops"
val metaData =
toMetaData( new Paragraph( new Text( content ), NoPosition ) )
.getOrElse( error("Did not convert the content to MetaData") )
metaData.data should equal (
Map( "key 1" -> " value",
"key2" -> "value",
"key 3" -> " long \n value",
"key4" -> "",
"" -> "oops" )
)
}
}
}
// The MetaDataConverterSpec imports
import org.scalatest.Spec
import org.scalatest.matchers.ShouldMatchers
import scala.util.parsing.input.NoPosition
MetaDatas
This enables the filterType method to work to find the MetaData.
// In com/tristanhunt/knockoff/extra/MetaDatas.scala
package com.tristanhunt.knockoff.extra
case object MetaDatas
extends BlockType[ MetaData ] { def wrappedClass = classOf[ MetaData ] }