Осваиваю vala. Без практической задачи мне программировать не интересно, поэтому реализовал то, в чём у меня сейчас была потребность.
Программа читает два набора .srt файлов на двух языках, и на основе сопоставления временных меток создаёт файл памяти перевода (translation memory) в формате tmx.
Программа работает, своё дело сделала, но мне интересно, что можно было написать проще/эффективнее/понятнее (например, мне не нравится, как организован двумерный динамический массив для имён файлов).
Много кода:
struct TextEvent
{
double begin;
double end;
string text;
}
struct TextPair
{
string orig;
string trans;
}
struct StringArray
{
string[] sa;
}
int main(string[] args)
{
string[] lang_codes={}; /* Languages */
StringArray[] lang_data={}; /* Source file names */
string out_file=null; /* Output file name */
int file_i=0; /* Source file number */
/* Process command line arguments */
for(int i=1; i < args.length; ++i)
{
if(args[i].has_prefix("-"))
{
/* It's an option */
string opt=args[i].substring(1);
if("o" == opt)
{
/* Output file name */
++i;
if(i < args.length)
out_file=args[i];
}
else if(2 == opt.length)
{
/* Language code */
lang_codes+=opt.up();
file_i=0;
}
else
{
/* Some shit */
stderr.printf("Invalid option: %s\n", args[i]);
return 1;
}
}
else
{
/* It's a file name */
if(0 == lang_codes.length)
{
/* Language code must go first */
stderr.printf("Language undefined!\n");
return 1;
}
if(lang_data.length <= file_i)
{
/* Allocate new set of files */
lang_data.resize(file_i + 1);
lang_data[file_i].sa={};
}
if(lang_data[file_i].sa.length < lang_codes.length)
/* Allocate space for file name */
lang_data[file_i].sa.resize(lang_codes.length);
/* Store file name */
lang_data[file_i].sa[lang_codes.length-1]=args[i];
++file_i;
}
}
/* Print source file names */
for(int lang_i=0; lang_i < lang_codes.length; ++lang_i)
{
stdout.printf("%s files:\n", lang_codes[lang_i]);
foreach(var ld in lang_data)
{
if(null == ld.sa[lang_i])
{
stderr.printf("File lists mismatch!\n");
return 1;
}
stdout.printf(" %s\n", ld.sa[lang_i]);
}
}
if(lang_codes.length != 2)
{
stderr.printf("There must be 2 input languages!\n");
return 1;
}
if(null == out_file)
{
stderr.printf("Missing output file name!\n");
return 1;
}
TextPair[] pairs={}; /* Translations */
/* Process file sets */
foreach(var ld in lang_data)
{
if(null == ld.sa[0] || null == ld.sa[1])
{
stderr.printf("File lists mismatch!\n");
return 1;
}
stdout.printf("Processing:\n %s\n %s\n", ld.sa[0], ld.sa[1]);
try
{
/* Read, parse and match */
match_events(ref pairs, ld.sa);
}
catch(FileError e)
{
stderr.printf("%s\n", e.message);
return 1;
}
}
/* Write XML */
stdout.printf("Writing: %s\n", out_file);
write_pairs(out_file, pairs, lang_codes);
return 0;
}
/* Process file set */
void match_events(ref TextPair[] pairs, string[] file_set) throws FileError
{
TextEvent[] orig, trans; /* Parsed srt data */
int i_trans=0, i_orig=0; /* String numbers */
double o_begin, o_end, o_mid; /* Original time interval */
double t_begin, t_end, t_mid; /* Translated time interval */
/* Read and parse files */
orig=parse_srt(file_set[0]);
trans=parse_srt(file_set[1]);
/* Process text events */
while(i_orig < orig.length && i_trans < trans.length)
{
/* Calculate original time interval */
o_begin=orig[i_orig].begin;
o_end=orig[i_orig].end;
o_mid=(o_begin + o_end) / 2.0;
/* Calculate transalted time interval */
t_begin=trans[i_trans].begin;
t_end=trans[i_trans].end;
t_mid=(t_begin + t_end) / 2.0;
/* Test for interval match */
if(t_mid > o_begin && t_mid < o_end || o_mid > t_begin && o_mid < t_end)
{
/* Store matched text */
push_pair(ref pairs, orig[i_orig].text, trans[i_trans].text);
++i_trans;
++i_orig;
/* Concatenate overlapping events */
do
{
while(i_trans < trans.length)
{
t_mid=(trans[i_trans].begin + trans[i_trans].end) / 2.0;
if(t_mid < o_begin || t_mid > o_end)
break;
pairs[pairs.length-1].trans+=" " + trans[i_trans].text;
t_end=trans[i_trans].end;
++i_trans;
}
while(i_orig < orig.length)
{
o_mid=(orig[i_orig].begin + orig[i_orig].end) / 2.0;
if(o_mid < t_begin || o_mid > t_end)
break;
pairs[pairs.length-1].orig+=" " + orig[i_orig].text;
o_end=orig[i_orig].end;
++i_orig;
}
}
while(t_mid > o_begin && t_mid < o_end && i_trans < trans.length);
}
else
{
/* No matching event */
if(o_mid < t_mid)
{
/* Skip original */
/*push_pair(ref pairs, orig[i_orig].text, "");*/
++i_orig;
}
else
{
/* Skip translated */
/*push_pair(ref pairs, "", trans[i_trans].text);*/
++i_trans;
}
}
}
}
/* Append text pair to array */
void push_pair(ref TextPair[] pairs, string t1, string t2)
{
pairs.resize(pairs.length+1);
pairs[pairs.length-1].orig=t1;
pairs[pairs.length-1].trans=t2;
}
/* Read and parse srt file */
TextEvent[] parse_srt(string file_name) throws FileError
{
TextEvent[] parsed={};
string file_text;
/* Read file */
FileUtils.get_contents(file_name, out file_text, null);
/* Parse text */
foreach(var srt_group in file_text.replace("\r", "").split("\n\n"))
{
var srt_part=srt_group.strip().split("\n", 3);
if(srt_part.length<3)
break;
var time_text=srt_part[1].split("-->", 2);
parsed.resize(parsed.length+1);
parsed[parsed.length-1].begin=parse_time_value(time_text[0]);
parsed[parsed.length-1].end=parse_time_value(time_text[1]);
parsed[parsed.length-1].text=srt_part[2].replace("\n", " ");
}
return parsed;
}
/* Convert srt time to double */
double parse_time_value(string time_text)
{
double time_value;
var time_parts=time_text.strip().replace(",", ".").split(":", 3);
time_value=double.parse(time_parts[2]);
time_value+=double.parse(time_parts[1])*60.0;
time_value+=double.parse(time_parts[0])*3600.0;
return time_value;
}
/* Write XML file */
void write_pairs(string file_name, TextPair[] pairs, string[] lang_codes)
{
var xml=new Xml.TextWriter.filename(file_name);
xml.set_indent(true);
xml.start_document();
xml.start_element("tmx");
xml.write_attribute("version", "1.4");
xml.write_attribute("xmlns", "http://www.localization.org/tmx14");
xml.start_element("header");
xml.write_attribute("creationtool", "srt2tmx");
xml.write_attribute("creationtoolversion", "0.1");
xml.write_attribute("datatype", "PlainText");
xml.write_attribute("segtype", "sentence");
xml.write_attribute("adminlang", "en-us");
xml.write_attribute("srclang", lang_codes[0]);
xml.write_attribute("o-tmf", "srt2tmx");
xml.end_element();
xml.start_element("body");
foreach(var p in pairs)
{
xml.start_element("tu");
xml.start_element("tuv");
xml.write_attribute("xml:lang", lang_codes[0]);
xml.write_element("seg", p.orig);
xml.end_element();
xml.start_element("tuv");
xml.write_attribute("xml:lang", lang_codes[1]);
xml.write_element("seg", p.trans);
xml.end_element();
xml.end_element();
}
xml.end_element();
xml.end_element();
xml.end_document();
xml.flush();
}