docs: def read_text_from_idx_file( file_name, layer_name=WORDS, keep_init_lines=False ):
      ''' Reads IDX format morphological annotations from given file, and returns as a Text 
          object.
          
          The Text object will be tokenized for paragraphs, sentences, words, and it will
          contain morphological annotations in the layer *layer_name* (by default: WORDS);
          
          Parameters
          -----------
          file_name : str
              Name of the input file; Should contain IDX format text segmentation and 
              morphological annotation;
          
          keep_init_lines : bool
              Optional argument specifying whether the lines from the file should also be
              preserved on a special layer named 'init_lines';
              Default: False
          
          layer_name : str
              Name of the Text's layer in which morphological annotations from text are 
              stored; 
              Defaults to WORDS;
      
          Example: expected format of the input:
            129	1	1	"	"	"	Z	
            129	2	1	Mul	mina	mina+l	P	sg ad
            129	3	1	on	olema	ole+0	V	b
            129	3	1	on	olema	ole+0	V	vad
            129	4	1	palju	palju	palju+0	D	
            129	5	1	igasugust	igasugune	iga_sugune+t	P	sg p
            129	6	1	informatsiooni	informatsioon	informatsioon+0	S	sg p
            129	7	1	.	.	.	Z	
          
      '''
      from nltk.tokenize.simple import LineTokenizer
      from nltk.tokenize.regexp import RegexpTokenizer
      from estnltk import Text
      # 1) Collect the text along with morphological analyses from the input IDX file
      init_lines = []
      words      = []
      sentence   = []
      sentences  = []
      prev_sent_id = -1
      prev_word_id = -1
      in_f = codecs.open(file_name, mode='r', encoding='utf-8')
      for line in in_f:
          fields = line.split('\t')
          assert len(fields) == 8, '(!) Unexpected number of fields in the line: '+str(len(fields))
          sent_id   = fields[0]
          word_id   = fields[1]
          clause_id = fields[2]
          token     = fields[3]
          if prev_sent_id != sent_id:
              # Record the old sentence, start a new 
              if sentence:
                 sentences.append( '  '.join(sentence) )
              sentence = []
          if prev_word_id != word_id:
              # Record a new token
              sentence.append( token )
              word = { TEXT:token, ANALYSIS:[] }
              words.append(word)
          # Augment the last word in the list with new analysis
          lemma  = fields[4]
          root   = fields[5]
          pos    = fields[6]
          form   = fields[7].rstrip()
          ending = ''
          clitic = ''
          analysis = get_analysis_dict( root, pos, form )
          analysis[LEMMA] = lemma
          words[-1][ANALYSIS].append( analysis )
          prev_sent_id = sent_id
          prev_word_id = word_id
          if keep_init_lines:
              init_lines.append( [sent_id+' '+word_id, line] )
      in_f.close()
      if sentence:
          # Record the last sentence
          sentences.append( '  '.join(sentence) )
  
      # 2) Construct the estnltk's Text
      kwargs4text = {
        # Use custom tokenization utils in order to preserve exactly the same 
        # tokenization as was in the input;
        "word_tokenizer":     RegexpTokenizer("  ", gaps=True),
        "sentence_tokenizer": LineTokenizer()
      }
      from estnltk.text import Text
      text = Text( '\n'.join(sentences), **kwargs4text )
      # Tokenize up to the words layer
      text.tokenize_words()
  
      # 3) Create a new layer with morphological analyses, or 
      #    populate the old layer with morphological analyses;
      assert len(text[WORDS]) == len(words), \
          '(!) Number of words from input does not match with the number of words in EstNLTK Text: '+\
               str(len(text[WORDS]) )+' != '+str(len(words))
      if layer_name != WORDS:
          # If necessary, create a new layer duplicating the WORDS layer
          text[layer_name] = []
          for word in text[WORDS]:
              text[layer_name].append({START:word[START], END:word[END], TEXT:word[TEXT]})
      # Copy morphological analyses to the new layer / populate the old layer
      for wid, word in enumerate( text[WORDS] ):
          text[layer_name][wid][ANALYSIS] = words[wid][ANALYSIS]
      if layer_name == WORDS:    
          assert text.is_tagged(ANALYSIS), '(!) The layer of analysis should exist by now!'
      
      if keep_init_lines:
          # Preserve the initial lines from file in a separate layer
          text['init_lines'] = []
          i = 0
          for wid, word in enumerate( text[layer_name] ):
              words_lines = []
              # collect lines associated with the word
              while i < len(init_lines):
                  [lid, line] = init_lines[i]
                  if not words_lines or words_lines[-1][0]==lid:
                      words_lines.append([lid, line])
                  else:
                      break
                  i += 1
              # record lines
              text['init_lines'].append( \
                  {START:word[START], END:word[END], 'lines':[l[1] for l in words_lines]} )
          assert len(text['init_lines']) == len(text[layer_name]), \
              '(!) The number of initial lines should match the number of words in text!'
      return text