예제 #1
0
void Solarix::PrintTags( SG_TagsList tags, lem::UFString &str, SynGram &sg )
{
 str.clear();

 if( tags!=NULL )
  {
   for( lem::Container::size_type i=0; i<tags->size(); ++i )
    {
     const int itag = (*tags)[i].first;
     const int ival = (*tags)[i].second;

     const ThesaurusTag &tag = sg.Get_Net().GetTagDefs()[itag];

     if( !str.empty() ) str += L' ';
     str += tag.GetName().c_str();

     if( ival!=UNKNOWN )
      {
       const lem::UCString &val = tag[ival];
       str += L'=';
       str += val.c_str();
      }
    }
  }

 return;
}
예제 #2
0
lem::Path lem::sqlite_get_absolute_filepath( const lem::UFString & connection_string )
{
    lem::Path sqlite_file;

    if( connection_string.eq_begi(L"path=") || connection_string.eq_begi(L"file=") )
    {
        lem::UFString db_path, db_file, basedir;

        lem::Collect<UFString> params;
        lem::parse( connection_string, params, L";" );
        for( lem::Container::size_type i=0; i<params.size(); ++i )
        {
            const lem::UFString & param = params[i];
            if( param.eq_begi(L"path=") )
                db_path = param.c_str()+5;
            else if( param.eq_begi(L"file=") )
                db_file = param.c_str()+5;
        }

        if( !db_path.empty() )
            sqlite_file = lem::Path(db_path);
        else
        {
            sqlite_file = lem::Path(basedir);
            sqlite_file.ConcateLeaf(db_file);
        }
    }
    else
    {
        sqlite_file = lem::Path(connection_string);
    }

    return sqlite_file;
}
예제 #3
0
// Считываем символы до закрывающей )
// Возвращает true, если последний токен перед ) был терминатором предложения.
bool SentenceBroker::ReadCharsUntilClosingParen(lem::UFString & line)
{
    int n_paren = 1;

    lem::UCString unbreakable;
    bool last_is_sentence_terminator = false;

    while (!Eof() || !chars.empty())
    {
        wchar_t c = GetChar();
        if (c == WEOF)
        {
            eof = true;
            break;
        }

        if (lem::is_uspace(c))
        {
            line += L' ';

            if (line.length() >= max_sentence_length)
                break;
        }
        else
        {
            // считаем символ или группу неразрывных символов.
            ReadCharOrUnbreakable(c, unbreakable);

            if (unbreakable == L')')
            {
                line += unbreakable.front();
                n_paren--;
                if (!n_paren)
                    break;
            }
            else if (unbreakable == L'(')
            {
                line += unbreakable.front();
                n_paren++;
                last_is_sentence_terminator = false;
            }
            else
            {
                line += unbreakable.c_str();
                last_is_sentence_terminator = sent_delims.find(unbreakable) != UNKNOWN;
            }

            if (line.length() >= max_sentence_length * 2)
                break;
        }
    }

    return last_is_sentence_terminator;
}
void WrittenTextAnalysisSession::AnalyzeIt(const lem::UFString & str, bool ApplyPatterns, bool DoSyntacticLinks, const ElapsedTimeConstraint & constraints)
{
    processed_str = str;

    if (params.LanguageUnknown())
    {
        // Язык предложения не задан явно, определим его на основе статистических критериев.
        const int id_language = dict->GetLexAuto().GuessLanguage(str);

        if (id_language == UNKNOWN)
        {
            lem::MemFormatter msg;
            msg.printf("Can not guess the language of the phrase [%us]", str.c_str());
            throw E_BaseException(msg.string());
        }

        params.SetLanguageID(id_language);

#if defined SOL_DEBUGGING
        if (trace != nullptr)
        {
            trace->LanguageGuessed(str, id_language);
        }
#endif
    }

    delete lexer;
    lexer = new WrittenTextLexer(str, params, dict, trace);

    Analyze(ApplyPatterns, DoSyntacticLinks, constraints);

    return;
}
WordFormEnumeratorGlobbing_File::WordFormEnumeratorGlobbing_File(
    WordEntries_File * _entries,
    const lem::UFString &_mask
)
    : WordFormEnumeratorListing_File(_entries), mask(_mask)
{
    LEM_CHECKIT_Z(!_mask.empty());
    return;
}
예제 #6
0
void CasingCoder::AddUpper( const CasingCoder::XLAT *xlat, lem::uint32_t src_ch, lem::UFString &res ) const
{
 for( lem::Container::size_type i=0; i<xlat->upper_xlat.size(); ++i )
  {
   lem::uint32_t res_ch = xlat->upper_xlat[i]->Apply(src_ch);
   if( res_ch!=src_ch )
    {
     Ucs4ToUCString x;
     x.Add(res_ch);
     res.Add_Dirty( x.ToString().c_str() );
     return;
    }
  }

 Ucs4ToUCString x;
 x.Add(src_ch);
 res.Add_Dirty( x.ToString().c_str() );

 return;
}
예제 #7
0
static bool IsHtmlClosed( const lem::UFString &tag )
{
 if( tags1.empty() )
  {
   const wchar_t* stags[] = { L"br", L"hr", L"link", L"meta", L"img", L"input",
                              NULL
                            };

   int i=0;
   while(stags[i]!=NULL)
    tags1.push_back( lem::UFString(stags[i++]) );
  }

 for( lem::Container::size_type i=0; i<tags1.size(); ++i )
  {
   const lem::UFString &t = tags1[i];
   if( tag.eq_begi(t) && (tag.length()==t.length() || tag[ t.length() ]==L' ' ) ) 
    return true;
  }

 return false;
}
예제 #8
0
SG_DeclensionForm::SG_DeclensionForm(
                                     const lem::UFString & lexem_generator,
                                     const lem::UFString & rx_condition,
                                     const lem::UFString & rx_flexer_flags,
                                     const CP_Array & dims
                                    )
 : dim(dims), form(lexem_generator.c_str())
{
 valid_condition = !rx_condition.empty();
 valid_flexer_flags = !rx_flexer_flags.empty();

 condition_str = rx_condition;
 flexer_flags_str = rx_flexer_flags;
 

 if( !condition_str.empty() )
  condition = boost::wregex( condition_str.c_str(), boost::basic_regex<wchar_t>::icase );

 if( !flexer_flags.empty() )
  flexer_flags = boost::wregex( flexer_flags_str.c_str(), boost::basic_regex<wchar_t>::icase );
 
 return; 
}
예제 #9
0
void SG_Language::SubstParadigmPattern(lem::UFString &pattern) const
{
    for (lem::Container::size_type i = 0; i < params.size(); ++i)
    {
        if (params.get(i)->name.eqi(L"ParadigmSubst"))
        {
            if (params[i]->values.size() != 2)
            {
                const Solarix::SG_LanguageParam & p = *params[i];
                // ...
                LEM_STOPIT;
            }

            const UFString &from = params[i]->values.front();
            const UFString &to = params[i]->values.back();
            pattern.subst_all(from, to);
        }
    }

    return;
}
예제 #10
0
void TreeScorerResult::DeserializeExpression( int expr_type, const lem::UFString & serialized )
{
 type=expr_type;

 if( type==NumberScoreType )
  {
   score = lem::to_int( serialized);
  }
 else if( type==NGramScoreType )
  {
   lem::StrParser<lem::UFString> txt(serialized);

   id_fact = lem::to_int( txt.read() );
   txt.read_it( L"(" );
   while( !txt.eof() )
   {
    if( txt.probe( L")" ) )
     break;

    if( !args.empty() )
     txt.read_it(L",");

    args.push_back( lem::UCString(txt.read().c_str()) );
   }
  }
 else if( type==FuncScoreType )
  {
   lem::MemReadHexStream bin2( serialized.c_str() );
   args.LoadBin(bin2);
   score_fun = TrFunCall::load_bin(bin2);
  }
 else
  {
   LEM_STOPIT;
  }

 return;
}
예제 #11
0
bool SyntaxShell::TryCommand( const lem::UFString &_str )
{
 LEM_CHECKIT_Z( !_str.empty() );

 if( _str==L"#help" || _str==L"?" )
  {
   ShowHelp();
   return true;
  }

 if( _str.front()!=L'#' )
  return false;

 if( _str.eq_beg( L"# " ) )
  return true; // комментарий


 if( _str.eq_beg( L"#timeout" ) )
  {
   lem::MCollect<UCString> toks;
   lem::parse( _str, toks, false );
   MaxTimeout = lem::to_int( toks[1] );
   return true;
  }

 if( _str.eq_beg( L"#maxalt" ) )
  {
   lem::MCollect<UCString> toks;
   lem::parse( _str, toks, false );
   MaxAlt = lem::to_int( toks[1] );
   lem::mout->printf( "MaxAlt=%d\n", MaxAlt );
   return true;
  }

 if( _str.eq_beg( L"#maxskiptoken" ) )
  {
   lem::MCollect<UCString> toks;
   lem::parse( _str, toks, false );
   MaxSkipToken = lem::to_int( toks[1] );

   lem::mout->printf( "MaxSkipToken=%d\n", MaxSkipToken );

   if( MaxSkipToken>0 )
    CompleteAnalysisOnly = false;

   if( MaxAlt==0 || MaxAlt==lem::int_max )
    {
     lem::mout->printf( "Attention: it is highly recommended to use %vfE#maxalt%vn NNN in order to limit the search tree depth\n" );
    }

   return true;
  }

 if( _str.eq_beg( L"#sem" ) )
  {
   lem::MCollect<UCString> toks;
   lem::parse( _str, toks, false );
   FindFacts = lem::to_bool( toks[1] );
   return true;
  }

 if( _str.eqi( L"#info" ) )
  {
   ShowDictionaryInfo();
   return true;
  }

 if( _str.eqi( L"#disconnect" ) )
  {
   sol_id.Delete();
   lem::mout->printf( "Dictionary database is disconnected.\n" );
   return true;
  }

 if( _str.eqi( L"#connect" ) )
  {
   LoadDictionary();
   return true;
  }

 if( _str.eq_begi( L"#tag" ) )
  {
   if( _str==L"#tag-" )
    {
     // Сбрасываем установленный фильтр
     tags_ptr.Delete();
     tags.clear();
     return true;
    }

   lem::Collect<lem::UFString> toks;
   lem::parse( UFString(_str.c_str()+4), toks, L"=" );
   UCString tag_name, tag_value;

   if( toks.size()>0 )
    tag_name = toks[0].c_str();

   if( toks.size()>1 )
    tag_value = toks[1].c_str();

   tag_name.trim();
   tag_value.trim();

   const int itag = sol_id->GetSynGram().Get_Net().FindTag(tag_name);
   if( itag==UNKNOWN )
    {
     lem::mout->printf( "Tag [%vfE%us%vn] not found\n", tag_name.c_str() );
     return true;
    }

   const ThesaurusTag &tt = sol_id->GetSynGram().Get_Net().GetTagDefs()[itag];

   if( tt.CountValues()>0 )
    { 
     int ivalue = tt[tag_value];
     if( ivalue==UNKNOWN )
      {
       lem::mout->printf( "Tag value [%vfE%us%vn] not found\n", tag_value.c_str() );
       return true;
      }
    }

   tags_ptr = new TF_TagOrNullFilter( *sol_id, tag_name, tag_value );
   return true;
  }


 if( _str.eq_begi( L"#param" ) )
  {
   if( _str==L"#param-" )
    {
     // Очищаем список параметров.
     params.clear();
     return true;
    }

   lem::Collect<lem::UFString> toks;
   lem::parse( UFString(_str.c_str()+7), toks, L"=" );
   UCString param_name, param_value;

   if( toks.size()>0 )
    param_name = toks[0].c_str();

   if( toks.size()>1 )
    param_value = toks[1].c_str();

   param_name.trim();
   param_value.trim();

   params.push_back( std::make_pair( param_name, param_value ) );

   return true;
  }



 lem::UFString str = lem::right( _str, _str.length()-1 );

 lem::zbool ret;

 if( str==L"debug" )
  {
   SetDebug(true);
   ret=true;
  }
 else if( str==L"nodebug" )
  {
   SetDebug(false);
   ret=true;
  }
 else if( str==L"traceon" )
  {
   SetDebug(true);
   traceon=true;
   debugger->Trace(true);
   ret=true;
  }
 else if( str==L"traceoff" )
  {
   traceon=false;

   if( debugger.NotNull() )
    debugger->Trace(true);

   ret=true;
  }
 else if( str==L"fuzzyon" )
  {
   allow_fuzzy = true;
   mout->printf( "Fuzzy projection is now %vfAON%vn\n" );
   ret=true;
  }
 else if( str==L"fuzzyoff" )
  {
   allow_fuzzy = false;
   mout->printf( "Fuzzy projection is now %vfDOFF%vn\n" );
   ret=true;
  }
 else if( str=="disable_filters" )
  {
   EnableFilters=false;
   ret = true;
  }
 else if( str=="enable_filters" )
  {
   EnableFilters=true;
   ret = true;
  }
 else if( str=="schedule1" )
  {
   CompleteAnalysisOnly=true;
   UseTopDownThenSparse=true;
   mout->printf( "Workflow=%vfATOP-DOWN, TOP-DOWN INCOMPLETE%vn\n" );
   ret=true;
  }
 else if( str==L"topdown" )
  {
   UseTopDownThenSparse=false;
   CompleteAnalysisOnly=true;
   mout->printf( "%vfAtop-down%vn analyzer is activated\n" );
   ret=true;
  }
 else if( str==L"allow_incomplete" )
  {
   CompleteAnalysisOnly = false;
   mout->printf( "Incomplete analysis is %vfAALLOWED%vn\n" );
   ret=true;
  }
 else if( str==L"disallow_incomplete" )
  {
   CompleteAnalysisOnly = true;
   mout->printf( "Incomplete analysis is %vfDDISALLOWED%vn\n" );
   ret=true;
  }
 else if( str==L"allow_reco" )
  {
   UseReconstructor = true;
   mout->printf( "Token reconstructor is %vfAALLOWED%vn\n" );
   ret=true;
  }
 else if( str==L"disallow_reco" )
  {
   UseReconstructor = false;
   mout->printf( "Token reconstructor is %vfDDISALLOWED%vn\n" );
   ret=true;
  }
 else if( str==L"allow_model" )
  {
   if( sol_id->GetLexAuto().GetModel().GetSequenceLabeler().IsAvailable() || sol_id->GetLexAuto().GetModel().GetClassifier().IsAvailable() )
    {
     ApplyModel = true;
     mout->printf( "Morphology model is enabled\n" );
    }
   else
    {
     mout->printf( "Morphology model is not available\n" );
    }

   ret=true;
  }
 else if( str==L"disallow_model" )
  {
   ApplyModel = false;
   mout->printf( "Morphology model is disabled\n" );
   ret=true;
  }
 else if( str==L"show" )
  {
   if( current_analysis.NotNull() )
    {
     const Res_Pack &pack = current_analysis->GetPack();

     mout->printf( "\nResult pack contains %vfE%d%vn variators:\n", pack.vars().size() );

     if( run_mode==MorphologyMode )
      {
       for( lem::Container::size_type i=0; i<pack.vars().size(); i++ )
        {
         const Variator * var = pack.vars()[i];
         for( lem::Container::size_type k=0; k<var->size(); ++k )
          {
           const Tree_Node & root = var->get(k);
           mout->printf( "%d: ", CastSizeToInt(k) );
           root.Print( *lem::mout, sol_id->GetSynGram(), -1, true );
           mout->eol();
          }

         mout->eol();
         mout->eol();
        }
      }
     else
      {
       for( lem::Container::size_type i=0; i<pack.vars().size(); i++ )
        {
         pack.vars()[i]->PrintV( *mout, sol_id->GetSynGram(), true );
         mout->eol();
         mout->eol();
        }
      }
    }
 
   ret=true;
  }
 else if( str==L"tree" )
  {
   if( current_analysis.NotNull() )
    {
     const Res_Pack &pack = current_analysis->GetPack();
     Solarix::print_syntax_tree( current_analysis->GetString(), current_analysis->GetPack(), *sol_id, *lem::mout, false, true );
    }
 
   ret=true;
  }
 else if( str.eq_beg("recog" ) )
  {
   if( current_analysis.NotNull() )
    {
     lem::mout->eol();
     current_analysis->GetLexer().PrintRecognitions( *lem::mout );
    }
 
   return true;
  }
 else if( str==L"tokenize" )
  {
   SetMode(TokenizerMode);
   ret=true;
  }
 else if( str==L"lemmatize" )
  {
   SetMode(LemmatizerMode);
   ret=true;
  }
 else if( str==L"speak" )
  {
   SetMode(SpeakerMode);
   ret=true;
  }
 else if( str==L"syntax" )
  {
   SetMode(SyntaxMode);
   ret=true;
  }
 else if( str==L"morphology" )
  {
   SetMode(MorphologyMode);
   ret=true;
  }
 else if( str==L"debugger" )
  {
   if( debugger.NotNull() )
    debugger->ManageBreakpoints();

   ret=true;
  }
 else
  {
   lem::mout->printf( "Invalid command %vfC%us%vn\n", str.c_str() );
   ret=true;
  }

 return ret;
}
예제 #12
0
void lem::sqlite_escape( lem::UFString &str )
{
    str.subst_all( L"'", L"''" );
    return;
}
예제 #13
0
void LexicalAutomat::TranslateLexem(
                                    lem::UFString &str,
                                    bool substitute_entries,
                                    int LanguageID
                                   )
{
 WideStringUcs4 cenum(str.c_str());
 lem::uint32_t c;
 lem::uint32_t prev_c=0;

 lem::Ucs4ToUFString yield;

 while( (c=cenum.Fetch())!=0 )
  {
   if( c==L' ' )
    {
     // Оставляем всегда единственный пробел (точнее - символ разделения лексем
     // в мультилексеме).
     if( prev_c!=Lexem::DelimiterChar )
      {
       yield.Add(Lexem::DelimiterChar);
       prev_c = c;
      }

     continue;
    }

   if( c==L'-' || c==L',' || c==L'\'' )
    {
     if( prev_c!=Lexem::DelimiterChar && prev_c!=0 )
      yield.Add(Lexem::DelimiterChar);

     yield.Add(c);

     if( cenum.Fetch()!=0 )
      {
       yield.Add( Lexem::DelimiterChar );
       cenum.Back();
      }

     prev_c = Lexem::DelimiterChar;

     continue;
    }

   Word_Coord wc;
   if( LanguageID==ANY_STATE ) 
    wc = GG->FindSymbol(c);
   else if( LanguageID==UNKNOWN )
    wc = GG->FindSymbol(c,GetDefaultAlphabets());
   else
    wc = GG->FindSymbol(c,LanguageID);

   if( wc.GetEntry()==UNKNOWN )
    {
     // Символ не найден в графической грамматике.
     yield.Add(c);
    }
   else
    {
     const GG_Entry &entry = GG->entries()[ wc.GetEntry() ];

     if(
        !substitute_entries ||
        wc.GetForm()==UNKNOWN ||
        wc.GetForm()==ANY_STATE
       )
      yield.Add( entry.GetForm(wc.GetForm()).GetName() );
     else
      yield.Add( entry.GetName() );
    }

   prev_c = c;
  }

 str = yield.ToString();

 return;
}
예제 #14
0
void SG_ComplexLink::LoadPoint( Macro_Parser &txtfile, SynGram &gram, lem::UFString &entry )
{
 BethToken t = txtfile.read();

 if( t.GetToken()==B_ENTRY )
  {
   // Особый формат entry Класс:Статья { уточнение }
   // преобразуется в ключ статьи и возвращается в виде #ключ
   UCString class0 = txtfile.read().string();
   const int ic0 = class0==L"?" ? ANY_STATE : gram.FindClass(class0);
   if( ic0==UNKNOWN ) 
    {
     Print_Error( txtfile );
     gram.GetIO().merr().printf( "Unknown class %us\n", class0.c_str() );
     throw E_BaseException();
    }

   txtfile.read_it( B_COLON );
   UCString entry0 = sol_read_multyname( gram.GetIO(), txtfile, B_OFIGPAREN );
   entry0.strip(L'"');
   entry0.trim();

   // Может быть задана дополнительная фильтрующая координата
   Solarix::CP_Array coords0;
   coords0.LoadTxt( txtfile, gram );
   
   if( gram.IsOmonym(ic0,lem::to_upper(entry0)) && coords0.empty() )
    {
     Print_Error( txtfile );
     gram.GetIO().merr().printf( "Omonym %us:%us requires the coordinate array\n", class0.c_str(), entry0.c_str() );
     throw E_BaseException();
    }

   const int ie0 = coords0.empty() ? gram.FindEntry(entry0,ic0,false) : gram.FindEntryOmonym(entry0,ic0,coords0);
   if( ie0==UNKNOWN ) 
    {
     Print_Error( txtfile );
     gram.GetIO().merr().printf( "Unknown entry %us:%us\n", class0.c_str(), entry0.c_str() );
     throw E_BaseException();
    }

   const int ekey = gram.GetEntry(ie0).GetKey();
   entry = lem::format_str( L"#%d", ekey );

   return;
  }

 bool figparen = t.GetToken()==B_OFIGPAREN;

 if( !figparen )
  txtfile.seekp(t);
 
 entry.reserve(128);

 if( t.string()==L'@' )
  {
   entry = L'@';
   t = txtfile.read();
  }

 while( !txtfile.eof() )
  {
   BethToken t = txtfile.read();
   if( figparen && t.GetToken()==B_CFIGPAREN )
    break;
   
   if( !entry.empty() )
    entry.Add_Dirty(L' ');

   UFString ts( t.GetFullStr() );
   ts.strip(L'"');
   entry.Add_Dirty( ts );

   if( !figparen )
    break;
  }

 entry.calc_hash();

 if( entry.front()==L'@' )
  {
   // Спецсимвол @ заставляет запомнить строку в виде "как есть"
   entry.remove(0);
   entry.trim();
  }
 else
  {
   entry.strip(L'"');
   gram.GetDict().GetLexAuto().TranslateLexem(entry,true);
  }

 return;
}
예제 #15
0
void CasingCoder::RestoreCasing( int external_casing_state, lem::UFString &res, int ekey )
{
 if( (ekey!=UNKNOWN && ekey==UnknownEntries_ekey ) )
  return;

 const XLAT *xlat = GetXLAT(ekey);

 switch( x(external_casing_state) )
 {
  case 1:
   {
    if( xlat->use_unicode )
     {
      res.to_Aa();
     }
    else
     {
      WideStringUcs4 src_enum( res.c_str() );
      lem::UFString out;
      out.reserve(res.length()+1);
      for( int i=0; ; ++i )
       {
        const lem::uint32_t src_ucs4 = src_enum.Fetch();
        if(!src_ucs4)
         break;

        if(i==0)
         AddUpper( xlat, src_ucs4, out );
        else
         AddLower( xlat, src_ucs4, out );
       }
      
      res = out;
     }

    break;
   }

  case 2:
   {
    if( xlat->use_unicode )
     {
      res.to_upper();
     }
    else
     {
      WideStringUcs4 src_enum( res.c_str() );
      lem::UFString out;
      out.reserve(res.length()+1);
      for(;;)
       {
        const lem::uint32_t src_ucs4 = src_enum.Fetch();
        if(!src_ucs4)
         break;

        AddUpper( xlat, src_ucs4, out );
       }
      
      res = out;
     }
 
    break;
   }

  case 3:
   {
    if( xlat->use_unicode )
     {
      Solarix::MakeEachLexemAa(res);
     }
    else
     {
      bool capitalize=true;
      
      WideStringUcs4 src_enum( res.c_str() );
      lem::UFString out;
      out.reserve(res.length()+1);
      for(;;)
       {
        const lem::uint32_t src_ucs4 = src_enum.Fetch();
        if(!src_ucs4)
         break;

        if( capitalize )
         {
          AddUpper( xlat, src_ucs4, out );
          capitalize=false;
         }
        else
         {
          AddLower( xlat, src_ucs4, out );

          if( src_ucs4==L' ' || src_ucs4==L'-' )
           capitalize=true;
         }
       }
      
      res = out;
     }

    break;
   }

  case 0:
  default:
   {
    if( xlat->use_unicode )
     {
      res.to_lower();
     }
    else
     {
      WideStringUcs4 src_enum( res.c_str() );
      lem::UFString out;
      out.reserve(res.length()+1);
      for(;;)
       {
        const lem::uint32_t src_ucs4 = src_enum.Fetch();
        if(!src_ucs4)
         break;

        AddLower( xlat, src_ucs4, out );
       }
      
      res = out;
     }

    break;
   }
 }

// res.subst_all( L" - ", L"-" );
// res.subst_all( L" ' ", L"'" );

 return;
}
예제 #16
0
bool SentenceBroker::Fetch(lem::UFString &line, int & line_paragraph_id)
{
    line.clear();
    line_paragraph_id = cur_paragraph_id;

    if (eof)
        return false;

    int n_quote = 0; // для учета символов "

    // Пропустим начальные пробелы.
    while (!eof)
    {
        wchar_t c = GetChar();

        if (c == WEOF)
        {
            break;
        }

        if (!lem::is_uspace(c) || IsEndOfSentenceMarker(c))
        {
            UngetChar(c);
            break;
        }
    }


    while (!eof || !chars.empty())
    {
        wchar_t c = GetChar();

        if (c == WEOF)
        {
            eof = true;
            return true;
        }
        else if (c == L' ')
        {
            line += c;
            continue;
        }
        else if (IsEndOfSentenceMarker(c))
        {
            line += c;
            break;
        }

        bool line_ready = false;

        if (
            (line.empty() || IsTokenDelimiter(line.back()) || IsTokenDelimiter(c)) &&
            tokenizer.NotNull() && tokenizer->IsUnbreakableFront(c)
            )
        {
            // Возможно далее идет исключительный случай. Выбираем символы из входного потока в 
            // попытке сконструировать максимально длинное исключение.
            lem::UCString substr;
            substr = c;

            while (!eof)
            {
                wchar_t c2 = GetChar();
                if (c2 == WEOF)
                {
                    // Достигли конца файла. Считали полный исключительный случай?
                    if (tokenizer->IsMatched(substr))
                    {
                        // Да!
                        line += substr.c_str();
                        c = c2;

                        // Считанный токен является разделителем предложений (типа ...)
                        if (sent_delims.find(substr) != UNKNOWN)
                        {
                            line.trim();

                            if (!line.empty() && (n_quote % 2) == 0)
                            {
                                count++;
                                return true;
                            }
                        }
                    }
                    else
                    {
                        // нет - вернем накопленные символы в поток чтения
                        for (int k = substr.length() - 1; k >= 0; --k)
                            UngetChar(substr[k]);

                        c = GetChar();
                    }

                    break;
                }

                substr += c2; // добавили еще один символ.

                // С символов substr начинается хоть одно исключение?
                if (!tokenizer->IsUnbreakableFront(substr) || substr.length() == lem::UCString::max_len)
                {
                    // Нет.
                    // Возможно, предыдущая подстрока является исключительной ситуацией.
                    UCString substr1 = lem::left(substr, substr.length() - 1);
                    if (tokenizer->IsMatched(substr1) && IsTokenDelimiter(substr.back()))
                    {
                        // Да!
                        line += substr1.c_str();

                        // Считанный токен является разделителем предложений (типа ...)
                        if (sent_delims.find(substr1) != UNKNOWN)
                        {
                            if ((n_quote % 2) == 0)
                            {
                                line.trim();

                                if (!line.empty())
                                {
                                    UngetChar(c2);
                                    count++;
                                    return true;
                                }
                            }
                            else
                            {
                                if (c2 == L'"')
                                {
                                    // Ситуация типа Кошка говорит "Мяу!" Собака говорит "Гав!"
                                    bool continuation_found = true;
                                    lem::MCollect<wchar_t> tmp_chars;
                                    while (!eof || !chars.empty())
                                    {
                                        const wchar_t c4 = GetChar();
                                        tmp_chars.push_back(c4);
                                        if (!lem::is_uspace(c4))
                                        {
                                            if (IsUpperChar(c4))
                                            {
                                                continuation_found = false;
                                            }

                                            break;
                                        }
                                    }

                                    for (int k = CastSizeToInt(tmp_chars.size()) - 1; k >= 0; --k)
                                        UngetChar(tmp_chars[k]);

                                    if (!continuation_found)
                                    {
                                        line += c2;
                                        return true;
                                    }
                                }
                            }
                        }

#if defined SOL_CAA
                        // Если считанный токен делит предложения в случае, когда за ним
                        // идет слово с первой заглавной буквой.
                        if (use_lexicon && casing_sent_delim.find(to_lower(substr1)) != UNKNOWN && CharCasingCoord != UNKNOWN && LowerCasingState != UNKNOWN)
                        {
                            Lexem next_token = PickNextToken();
                            if (IsUpperChar(next_token.front()))
                            {
                                la->TranslateLexem(next_token);

                                int ie = UNKNOWN;
                                if (seeker != NULL)
                                {
                                    ie = seeker->Find(next_token, false);
                                }
                                else
                                {
                                    MCollect<Word_Coord> found_list;
                                    la->ProjectWord(next_token, found_list, LanguageID);
                                    if (!found_list.empty())
                                        ie = found_list.front().GetEntry();
                                }

                                if (ie != UNKNOWN)
                                {
                                    const Solarix::SG_Entry &e = sg->GetEntry(ie);

                                    const int casing = e.attrs().FindOnce(Solarix::GramCoordAdr(CharCasingCoord));
                                    if (casing == LowerCasingState || casing == UNKNOWN)
                                    {
                                        UngetChar(c2);
                                        count++;
                                        return true;
                                    }
                                }
                            }
                        }
#endif

                        c = c2;
                    }
                    else
                    {
                        // Возвращаем все загруженные символы обратно в поток.
                        for (int k = substr.length() - 1; k >= 0; --k)
                            UngetChar(substr[k]);

                        c = GetChar();
                    }

                    break;
                }
            }
        }

        if (c == WEOF)
        {
            // закончился исходный текст, принудительно прерываем текущее предложение.
            eof = true;
            return true;
        }

        const bool ItIsSentDelim = sent_delims1.find(c) != UNKNOWN;
        if (ItIsSentDelim)
        {
            if (IsEndOfSentenceMarker(c))
                break;

            // Обычный конец предложения. Для точки надо проверять, если сразу после точки идет цифра
            // или символ в нижнем регистре, то это НЕ конец предложения.
            bool breaker = false;
            bool add_char = true;

            // Если у нас есть незакрытая пара ", то проверим следующий непустой символ.
            if ((n_quote % 2) != 0)
            {
                const wchar_t c2 = GetChar();
                // Если это закрывающая "
                if (c2 == L'"')
                {
                    n_quote++;
                    line += c;
                    line += c2;

                    const wchar_t c4 = GetChar();
                    if (sent_delims1.find(c4) != UNKNOWN)
                    {
                        line += c4;
                        count++;
                        return true;
                    }
                    else
                    {
                        UngetChar(c4);
                    }

                    // если дальше - пробел, и после него идет символ в нижнем регистре, то это не конец предложения.
                    bool continuation_found = true;
                    UFString tmp_chars;
                    while (!eof)
                    {
                        const wchar_t c5 = GetChar();
                        if (c5 == WEOF)
                        {
                            break;
                        }

                        tmp_chars += c5;
                        if (!lem::is_uspace(c5))
                        {
                            // найден не-пробельный символ.
                            if (IsUpperChar(c5))
                            {
                                continuation_found = false;
                            }

                            break;
                        }
                    }

                    // возвращаем все символы обратно
                    for (int i = CastSizeToInt(tmp_chars.size()) - 1; i >= 0; --i)
                        UngetChar(tmp_chars[i]);

                    if (!continuation_found)
                    {
                        // обрываем предложение.
                        line.trim();
                        count++;
                        return true;
                    }
                }
                else
                {
                    // нет - продолжим считывание символов предложения.
                    line += c;
                    line += c2;
                }
            }
            else
            {
#if defined SOL_CAA
                if (tokenizer.NotNull() && seeker.NotNull() && use_lexicon && c == L'.')
                {
                    // надо выделить слово, предшествующее точке. идем влево до разделителя токенов.
                    int icur = line.length() - 1;
                    while (icur >= 0)
                    {
                        if (tokenizer->IsTokenDelimiter(line[icur]))
                            break; // нашли начало последнего слова
                        else
                            icur--; // сдвигаемся влево
                    }

                    Solarix::Lexem last_word;
                    for (int j = icur + 1; j < line.length(); ++j)
                        last_word.Add_Dirty(line[j]);

                    last_word.calc_hash();

                    la->TranslateLexem(last_word);

                    if (seeker->Find(last_word, false) != UNKNOWN)
                    {
                        breaker = true;
                    }
                }
#endif 

                if (!breaker)
                {
                    breaker = true;
                    wchar_t c2 = PeekChar();
                    if (c == L'.')
                    {
                        if (lem::is_udigit(c2))
                        {
                            breaker = false;
                        }
                        else if (IsLowerChar(c2))
                        {
                            breaker = false;
                        }
                        else if (c2 == L',')
                        {
                            breaker = false;
                        }
                        else if (lem::is_uspace(c2))
                        {
                            // Дойдем до первого не-пробельного символа.
                            line += c;

                            add_char = false;
                            while (c != WEOF)
                            {
                                c = GetChar();
                                line += c;
                                if (IsEndOfSentenceMarker(c))
                                    break;

                                c = PeekChar();

                                if (!lem::is_uspace(c))
                                {
                                    if (IsLowerChar(c))
                                    {
                                        breaker = false;
                                    }

                                    break;
                                }
                            }

                            c = L'.';
                        }

                        wchar_t c0 = c;
                        c2 = PeekChar();
                        if (c2 == c0)
                        {
                            line += c;
                            add_char = false;
                            while (c != WEOF && c == c0)
                            {
                                c = GetChar();
                                line += c;
                                c = PeekChar();
                            }
                        }
                    }
                    else if (c == '!' || c == '?')
                    {
                        wchar_t c0 = c;
                        c2 = PeekChar();
                        if (c2 == L'?' || c2 == L'!') // То есть токены типа !!! и !?
                        {
                            line += c;
                            add_char = false;
                            while (c != WEOF && (c == L'!' || c == L'?'))
                            {
                                c = GetChar();
                                line += c;
                                c = PeekChar();
                            }
                        }
                    }
                }

                if (c == WEOF)
                {
                    eof = true;
                    add_char = false;
                }

                if (breaker)
                {
                    line_ready = true;
                }

                if (add_char)
                    line += c;
            }
        }
        else if (line.length() > max_sentence_length && (lem::is_uspace(c) || c == L',' || c == L'-' || c == L';' || c == L':'))
        {
            // Слишком длинные предложения обрываем на безопасных символах.
            line_ready = true;
            line += c;
        }
        else if (c == L'\r' || c == L'\n' || c == L'\t' || c == L'\b')
        {
            // некоторые управляющие символы заменяем пробелами
            line += L' ';
        }
        else
        {
            line += c;

            if (c == L'"')
                n_quote++;
            else if (c == L'(')
            {
                // если предложение начинается с (, то надо смотреть, какой токен будет перед ), и если это терминатор - обрывать предложение.
                if (line.size() == 1)
                {
                    if (ReadCharsUntilClosingParen(line))
                    {
                        line.trim();
                        return true;
                    }
                }
                else
                {
                    ReadCharsUntilClosingParen(line);
                }
            }
        }

        if (line_ready)
        {
            if (line.length() > 0 && IsEndOfParagraphMarker(line.last_char()))
            {
                line.remove(line.length() - 1);
                cur_paragraph_id++;
            }

            line.trim();

            if (!line.empty())
                count++;

            return true;
        }
    }

    if (line.length() > 0 && IsEndOfParagraphMarker(line.last_char()))
    {
        line.remove(line.length() - 1);
        cur_paragraph_id++;
    }

    line.trim();

    if (!line.empty())
        count++;

    return true;
}
int SegmentingSentenceTokenizer::MatchLen(const lem::UFString & s, int i0) const
{
    return lookup->match_len(s.c_str() + i0);
}