///|
fn gff3_escape_value(value : String) -> String {
value
.replace(old="%", new="%25")
.replace(old=";", new="%3B")
.replace(old="=", new="%3D")
.replace(old=" ", new="%20")
}
///|
fn gtf_escape_value(value : String) -> String {
value.replace(old="\\", new="\\\\").replace(old="\"", new="\\\"")
}
///|
fn option_to_field(value : String?) -> String {
match value {
Some(text) => text
None => "."
}
}
///|
fn gff3_attribute_text(attributes : Array[Attribute]) -> String {
if attributes.length() == 0 {
"."
} else {
let parts : Array[String] = []
for attr in attributes {
if attr.value.is_empty() {
parts.push(gff3_escape_value(attr.key))
} else {
parts.push(
"\{gff3_escape_value(attr.key)}=\{gff3_escape_value(attr.value)}",
)
}
}
parts.join(";")
}
}
///|
fn gtf_attribute_text(attributes : Array[Attribute]) -> String {
if attributes.length() == 0 {
"."
} else {
let parts : Array[String] = []
for attr in attributes {
parts.push("\{attr.key} \"\{gtf_escape_value(attr.value)}\"")
}
parts.join("; ") + ";"
}
}
///|
pub fn Feature::to_gff3_line(self : Feature) -> String {
[
self.seqid,
self.source,
self.feature_type,
"\{self.start}",
"\{self.end}",
option_to_field(self.score),
self.strand.to_string(),
option_to_field(self.phase),
gff3_attribute_text(self.attributes),
].join("\t")
}
///|
pub fn Feature::to_gtf_line(self : Feature) -> String {
[
self.seqid,
self.source,
self.feature_type,
"\{self.start}",
"\{self.end}",
option_to_field(self.score),
self.strand.to_string(),
option_to_field(self.phase),
gtf_attribute_text(self.attributes),
].join("\t")
}
///|
pub fn Annotation::to_gff3(self : Annotation) -> String {
let lines : Array[String] = ["##gff-version 3"]
for feature in self.features {
lines.push(feature.to_gff3_line())
}
lines.join("\n") + "\n"
}
///|
pub fn Annotation::to_gtf(self : Annotation) -> String {
let lines : Array[String] = []
for feature in self.features {
lines.push(feature.to_gtf_line())
}
if lines.length() == 0 {
""
} else {
lines.join("\n") + "\n"
}
}
///|
pub fn Feature::to_debug_tsv(self : Feature) -> String {
[
self.seqid,
self.source,
self.feature_type,
"\{self.start}",
"\{self.end}",
self.strand.to_string(),
"\{self.attributes.length()}",
].join("\t")
}
///|
pub fn Annotation::feature_table_tsv(self : Annotation) -> String {
let lines : Array[String] = [
"seqid\tsource\tfeature_type\tstart\tend\tstrand\tattribute_count",
]
for feature in self.features {
lines.push(feature.to_debug_tsv())
}
lines.join("\n") + "\n"
}
///|
pub fn Annotation::gene_table_tsv(self : Annotation) -> String {
let lines : Array[String] = [
"gene_id\tgene_name\tseqid\tstart\tend\tstrand\ttranscripts",
]
for gene in self.to_gene_models() {
lines.push(
[
gene.id,
gene.name.unwrap_or(""),
gene.feature.seqid,
"\{gene.feature.start}",
"\{gene.feature.end}",
gene.feature.strand.to_string(),
"\{gene.transcripts.length()}",
].join("\t"),
)
}
lines.join("\n") + "\n"
}
///|
pub fn Annotation::transcript_table_tsv(self : Annotation) -> String {
let lines : Array[String] = [
"gene_id\ttranscript_id\ttranscript_name\tseqid\tstart\tend\tstrand\texons\tcds",
]
for gene in self.to_gene_models() {
for tx in gene.transcripts {
lines.push(
[
gene.id,
tx.id,
tx.name.unwrap_or(""),
tx.feature.seqid,
"\{tx.feature.start}",
"\{tx.feature.end}",
tx.feature.strand.to_string(),
"\{tx.exons.length()}",
"\{tx.cds.length()}",
].join("\t"),
)
}
}
lines.join("\n") + "\n"
}