|
|
| version 1.226, 2009/05/15 06:57:50 | version 1.240, 2012/03/16 09:24:14 |
|---|---|
| Line 1 | Line 1 |
| /** @file | /** @file |
| Parser: string class. @see untalength_t.C. | Parser: string class. @see untalength_t.C. |
| Copyright (c) 2001-2009 ArtLebedev Group (http://www.artlebedev.com) | Copyright (c) 2001-2012 Art. Lebedev Studio (http://www.artlebedev.com) |
| Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru) | Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru) |
| */ | */ |
| static const char * const IDENT_STRING_C="$Date$"; | |
| #include "pa_string.h" | #include "pa_string.h" |
| #include "pa_exception.h" | #include "pa_exception.h" |
| #include "pa_table.h" | #include "pa_table.h" |
| Line 14 static const char * const IDENT_STRING_C | Line 12 static const char * const IDENT_STRING_C |
| #include "pa_charset.h" | #include "pa_charset.h" |
| #include "pa_vregex.h" | #include "pa_vregex.h" |
| volatile const char * IDENT_PA_STRING_C="$Id$" IDENT_PA_STRING_H; | |
| const String String::Empty; | const String String::Empty; |
| int pa_atoi(const char* str, const String* problem_source) { | int pa_atoi(const char* str, const String* problem_source) { |
| Line 166 String::Body String::Body::Format(int va | Line 166 String::Body String::Body::Format(int va |
| } | } |
| String::Body String::Body::trim(String::Trim_kind kind, const char* chars, | String::Body String::Body::trim(String::Trim_kind kind, const char* chars, |
| size_t* out_start, size_t* out_length) const { | size_t* out_start, size_t* out_length, Charset* source_charset) const { |
| size_t our_length=length(); | size_t our_length=length(); |
| if(!our_length) | if(!our_length) |
| return *this; | return *this; |
| if(!chars) | |
| chars=" \t\n"; // white space | // check if any UTF-8 in chars |
| bool fast=true; | |
| if(chars && source_charset && source_charset->isUTF8()){ | |
| const char* pos=chars; | |
| while(unsigned char c=*pos++) | |
| if(c>127){ | |
| fast=false; | |
| break; | |
| } | |
| } | |
| size_t start=0; | size_t start=0; |
| size_t end=our_length; | size_t end=our_length; |
| // from left... | if(!chars) |
| if(kind!=TRIM_END) { | chars=" \t\n"; // white space |
| CORD_pos pos; set_pos(pos, 0); | |
| while(true) { | if(fast){ |
| char c=CORD_pos_fetch(pos); | // from left... |
| if(strchr(chars, c)) { | if(kind!=TRIM_END) { |
| if(++start==our_length) | CORD_pos pos; set_pos(pos, 0); |
| return 0; // all chars are empty, just return empty string | while(true) { |
| } else | char c=CORD_pos_fetch(pos); |
| break; | if(strchr(chars, c)) { |
| if(++start==our_length) | |
| CORD_next(pos); | return 0; // all chars are empty, just return empty string |
| } | } else |
| } | break; |
| // from right.. | |
| if(kind!=TRIM_START) { | CORD_next(pos); |
| CORD_pos pos; set_pos(pos, end-1); | } |
| while(true) { | } |
| char c=CORD_pos_fetch(pos); | |
| if(strchr(chars, c)) { | // from right.. |
| if(--end==0) // optimization: NO need to check for 'end>=start', that's(<) impossible | if(kind!=TRIM_START) { |
| return 0; // all chars are empty, just return empty string | CORD_pos pos; set_pos(pos, end-1); |
| } else | while(true) { |
| break; | char c=CORD_pos_fetch(pos); |
| if(strchr(chars, c)) { | |
| if(--end==0) // optimization: NO need to check for 'end>=start', that's(<) impossible | |
| return 0; // all chars are empty, just return empty string | |
| } else | |
| break; | |
| CORD_prev(pos); | CORD_prev(pos); |
| } | |
| } | |
| } else { | |
| const XMLByte* src_begin=(const XMLByte*)cstr(); | |
| const XMLByte* src_end=src_begin+our_length; | |
| // from left... | |
| if(kind!=TRIM_END) { | |
| while(src_begin<src_end){ | |
| uint char_length=1; | |
| const XMLByte* ptr=src_begin; | |
| // searching first UTF-8 byte: http://tools.ietf.org/html/rfc3629#section-3 | |
| while(++src_begin<=src_end && (*src_begin>127 && *src_begin<0xC0)) | |
| char_length++; | |
| bool found=false; | |
| for(const char* chars_byte=chars; chars_byte=strchr(chars_byte, *ptr); chars_byte++) | |
| if(strncmp(chars_byte, (const char*)ptr, char_length)==0){ | |
| found=true; | |
| break; | |
| } | |
| if(found){ | |
| start+=char_length; | |
| if(start==our_length) | |
| return 0; // all chars are empty, just return empty string | |
| } else | |
| break; | |
| } | |
| } | |
| // from right.. | |
| if(kind!=TRIM_START) { | |
| while(src_begin<src_end){ | |
| uint char_length=1; | |
| // searching first UTF-8 byte: http://tools.ietf.org/html/rfc3629#section-3 | |
| while(src_begin<=--src_end && (*src_end>127 && *src_end<0xC0)) | |
| char_length++; | |
| bool found=false; | |
| for(const char* chars_byte=chars; chars_byte=strchr(chars_byte, *src_end); chars_byte++) | |
| if(strncmp(chars_byte, (const char*)src_end, char_length)==0){ | |
| found=true; | |
| break; | |
| } | |
| if(found){ | |
| end-=char_length; | |
| if(end==0) | |
| return 0; // all chars are empty, just return empty string | |
| } else | |
| break; | |
| } | |
| } | } |
| } | } |
| Line 284 String& String::append_strdup(const char | Line 351 String& String::append_strdup(const char |
| return *this; | return *this; |
| } | } |
| int CORD_batched_len(const char* s, size_t* len){ | struct CORD_length_info { |
| (*len) += lengthUTF8( (const XMLByte *)s, (const XMLByte *)s+strlen(s)); | size_t len; |
| return 0; | size_t skip; |
| }; | |
| int CORD_batched_len(const char* s, CORD_length_info* info){ | |
| info->len += lengthUTF8( (const XMLByte *)s, (const XMLByte *)s+strlen(s)); return 0; | |
| } | } |
| // can be called only for IS_FUNCTION(CORD) which are used only in Lang | // can be called only for IS_FUNCTION(CORD) which are used in large String::Body::mid |
| int CORD_batched_len(const char, size_t *len){ | int CORD_batched_len(const char c, CORD_length_info* info){ |
| (*len)++; | if (info->skip==0){ |
| info->len++; | |
| info->skip = lengthUTF8Char(c)-1; | |
| } else { | |
| info->skip--; | |
| } | |
| return 0; | return 0; |
| } | } |
| size_t String::length(Charset& charset) const { | size_t String::length(Charset& charset) const { |
| if(charset.isUTF8()){ | if(charset.isUTF8()){ |
| size_t len=0; | CORD_length_info info = {0, 0}; |
| body.for_each<size_t *>(CORD_batched_len, CORD_batched_len, &len); | body.for_each<CORD_length_info *>(CORD_batched_len, CORD_batched_len, &info); |
| return len; | return info.len; |
| } else | } else |
| return body.length(); | return body.length(); |
| } | } |
| Line 342 String& String::mid(Charset& charset, si | Line 418 String& String::mid(Charset& charset, si |
| return result; | return result; |
| if(charset.isUTF8()){ | if(charset.isUTF8()){ |
| const XMLByte* srcPtr=(const XMLByte*)cstrm(); | const XMLByte* src_begin=(const XMLByte*)cstr(); |
| const XMLByte* srcEnd=srcPtr+body.length(); | const XMLByte* src_end=src_begin+body.length(); |
| // convert 'from' and 'substr_length' from 'characters' to 'bytes' | // convert 'from' and 'substr_length' from 'characters' to 'bytes' |
| from=getUTF8BytePos(srcPtr, srcEnd, from); | from=getUTF8BytePos(src_begin, src_end, from); |
| substr_length=getUTF8BytePos(srcPtr+from, srcEnd, substr_length); | substr_length=getUTF8BytePos(src_begin+from, src_end, substr_length); |
| if(!substr_length) | if(!substr_length) |
| return result; | return result; |
| } | } |
| Line 384 size_t String::pos(const String& substr, | Line 460 size_t String::pos(const String& substr, |
| size_t String::pos(Charset& charset, const String& substr, | size_t String::pos(Charset& charset, const String& substr, |
| size_t this_offset, Language lang) const { | size_t this_offset, Language lang) const { |
| size_t result; | |
| if(charset.isUTF8()){ | if(charset.isUTF8()){ |
| const XMLByte* srcPtr=(const XMLByte*)cstrm(); | const XMLByte* srcPtr=(const XMLByte*)cstr(); |
| const XMLByte* srcEnd=srcPtr+body.length(); | const XMLByte* srcEnd=srcPtr+body.length(); |
| // convert 'this_offset' from 'characters' to 'bytes' | // convert 'this_offset' from 'characters' to 'bytes' |
| this_offset=getUTF8BytePos(srcPtr, srcEnd, this_offset); | this_offset=getUTF8BytePos(srcPtr, srcEnd, this_offset); |
| result=pos(substr.body, this_offset, lang); | size_t result=pos(substr.body, this_offset, lang); |
| if(result==CORD_NOT_FOUND) | return (result==CORD_NOT_FOUND) |
| return STRING_NOT_FOUND; | ? STRING_NOT_FOUND |
| : getUTF8CharPos(srcPtr, srcEnd, result); // convert 'result' from 'bytes' to 'characters' | |
| // convert 'result' from 'bytes' to 'characters' | |
| result=getUTF8CharPos(srcPtr, srcEnd, result); | |
| } else { | } else { |
| result=pos(substr.body, this_offset, lang); | size_t result=pos(substr.body, this_offset, lang); |
| if(result==CORD_NOT_FOUND) | return (result==CORD_NOT_FOUND) |
| return STRING_NOT_FOUND; | ? STRING_NOT_FOUND |
| : result; | |
| } | } |
| return result; | |
| } | } |
| void String::split(ArrayString& result, | void String::split(ArrayString& result, |
| size_t& pos_after, | size_t& pos_after, |
| const char* delim, | const char* delim, |
| Language lang, int limit) const { | Language lang, int limit) const { |
| if(is_empty()) | |
| return; | |
| size_t self_length=length(); | size_t self_length=length(); |
| if(size_t delim_length=strlen(delim)) { | if(size_t delim_length=strlen(delim)) { |
| size_t pos_before; | size_t pos_before; |
| Line 434 void String::split(ArrayString& result, | Line 508 void String::split(ArrayString& result, |
| size_t& pos_after, | size_t& pos_after, |
| const String& delim, Language lang, | const String& delim, Language lang, |
| int limit) const { | int limit) const { |
| if(is_empty()) | |
| return; | |
| if(!delim.is_empty()) { | if(!delim.is_empty()) { |
| size_t pos_before; | size_t pos_before; |
| // while we have 'delim'... | // while we have 'delim'... |
| Line 462 Table* String::match(VRegex* vregex, | Line 538 Table* String::match(VRegex* vregex, |
| bool global=vregex->is_global_search(); | bool global=vregex->is_global_search(); |
| const char* subject=cstr(); | const char* subject=cstr(); |
| size_t subject_length=strlen(subject); | size_t subject_length=length(); |
| const int ovector_size=(1/*match*/+MAX_MATCH_GROUPS)*3; | const int ovector_size=(1/*match*/+MAX_MATCH_GROUPS)*3; |
| int ovector[ovector_size]; | int ovector[ovector_size]; |
| Line 480 Table* String::match(VRegex* vregex, | Line 556 Table* String::match(VRegex* vregex, |
| int prefinish=ovector[0]; | int prefinish=ovector[0]; |
| poststart=ovector[1]; | poststart=ovector[1]; |
| ArrayString* row=new ArrayString; | |
| if (prestart==poststart && subject[poststart]=='\n'){ | |
| prestart++; | |
| continue; | |
| } | |
| ArrayString* row=new ArrayString(3); | |
| if(need_pre_post_match) { | if(need_pre_post_match) { |
| *row+=&mid(0, prefinish); // .prematch column value | *row+=&mid(0, prefinish); // .prematch column value |
| *row+=&mid(prefinish, poststart); // .match | *row+=&mid(prefinish, poststart); // .match |
| Line 493 Table* String::match(VRegex* vregex, | Line 575 Table* String::match(VRegex* vregex, |
| for(int i=1; i<exec_result; i++) { | for(int i=1; i<exec_result; i++) { |
| // -1:-1 case handled peacefully by mid() itself | // -1:-1 case handled peacefully by mid() itself |
| *row+=&mid(ovector[i*2+0], ovector[i*2+1]); // .i column value | *row+=(ovector[i*2+0]>=0 && ovector[i*2+1]>0)?&mid(ovector[i*2+0], ovector[i*2+1]):new String; // .i column value |
| } | } |
| matches_count++; | matches_count++; |
| Line 573 const String& String::escape(Charset& so | Line 655 const String& String::escape(Charset& so |
| return Charset::escape(*this, source_charset); | return Charset::escape(*this, source_charset); |
| } | } |
| #define STRING_APPEND(result, from_cstr, langs, langs_offset, length) \ | |
| result.langs.append(result.body, langs, langs_offset, length); \ | |
| result.body.append_strdup_know_length(from_cstr, length); | |
| const String& String::replace(const Dictionary& dict) const { | const String& String::replace(const Dictionary& dict) const { |
| if(!dict.count() || is_empty()) | |
| return *this; | |
| String& result=*new String(); | String& result=*new String(); |
| const char* old_cstr=cstr(); | const char* old_cstr=cstr(); |
| const char* prematch_begin=old_cstr; | const char* prematch_begin=old_cstr; |
| const char* current=old_cstr; | if(dict.count()==1) { |
| while(*current) { | // optimized simple case |
| if(Dictionary::Subst subst=dict.first_that_begins(current)) { | |
| Dictionary::Subst subst=dict.get(0); | |
| while(const char* p=strstr(prematch_begin, subst.from)) { | |
| // prematch | // prematch |
| if(size_t prematch_length=current-prematch_begin) { | if(size_t prematch_length=p-prematch_begin) { |
| result.langs.append(result.body, langs, prematch_begin-old_cstr, prematch_length); | STRING_APPEND(result, prematch_begin, langs, prematch_begin-old_cstr, prematch_length) |
| result.body.append_strdup_know_length(prematch_begin, prematch_length); | |
| } | } |
| // match | // match |
| // skip 'a' in 'current'; move prematch_begin | prematch_begin=p+subst.from_length; |
| current+=subst.from_length; prematch_begin=current; | |
| if(const String* b=subst.to) // are there any b? | if(const String* b=subst.to) // are there any b? |
| result<<*b; | result<<*b; |
| } else // simply advance | } |
| current++; | |
| } else { | |
| const char* current=old_cstr; | |
| while(*current) { | |
| if(Dictionary::Subst subst=dict.first_that_begins(current)) { | |
| // prematch | |
| if(size_t prematch_length=current-prematch_begin) { | |
| STRING_APPEND(result, prematch_begin, langs, prematch_begin-old_cstr, prematch_length) | |
| } | |
| // match | |
| // skip 'a' in 'current'; move prematch_begin | |
| current+=subst.from_length; prematch_begin=current; | |
| if(const String* b=subst.to) // are there any b? | |
| result<<*b; | |
| } else // simply advance | |
| current++; | |
| } | |
| } | } |
| if(prematch_begin==old_cstr) // not modified | |
| return *this; | |
| // postmatch | // postmatch |
| if(size_t postmatch_length=current-prematch_begin) { | if(size_t postmatch_length=old_cstr+length()-prematch_begin) { |
| result.langs.append(result.body, langs, prematch_begin-old_cstr, postmatch_length); | STRING_APPEND(result, prematch_begin, langs, prematch_begin-old_cstr, postmatch_length) |
| result.body.append_strdup_know_length(prematch_begin, postmatch_length); | |
| } | } |
| ASSERT_STRING_INVARIANT(result); | ASSERT_STRING_INVARIANT(result); |
| Line 675 bool String::deserialize(size_t prolog_s | Line 786 bool String::deserialize(size_t prolog_s |
| if(cur[body_length] != 0) // in place? | if(cur[body_length] != 0) // in place? |
| return false; | return false; |
| // 3: letters | // 3: letters |
| body=String::Body(cur); | body=String::Body(*cur?cur:0); |
| #ifdef STRING_LENGTH_CACHING | |
| body.set_length(body_length); | |
| #endif | |
| cur+=body_length+1; | cur+=body_length+1; |
| in_buf-=body_length+1; | in_buf-=body_length+1; |
| Line 722 bool String::deserialize(size_t prolog_s | Line 836 bool String::deserialize(size_t prolog_s |
| } | } |
| const char* String::Body::v() const { | const char* String::Body::v() const { |
| return CORD_to_const_char_star(body); | return CORD_to_const_char_star(body, length()); |
| } | } |
| void String::Body::dump() const { | void String::Body::dump() const { |
| CORD_dump(body); | CORD_dump(body); |
| Line 730 void String::Body::dump() const { | Line 844 void String::Body::dump() const { |
| const char* String::Languages::v() const { | const char* String::Languages::v() const { |
| if(opt.is_not_just_lang) | if(opt.is_not_just_lang) |
| return CORD_to_const_char_star(langs); | return CORD_to_const_char_star(langs, 0); |
| else | else |
| return (const char*)&langs; | return (const char*)&langs; |
| } | } |
| Line 754 const char* String::v() const { | Line 868 const char* String::v() const { |
| return buf; | return buf; |
| } | } |
| void String::dump() const { | void String::dump() const { |
| body.dump(); | body.dump(); |
| langs.dump(); | langs.dump(); |
| } | } |
| const String& String::trim(String::Trim_kind kind, const char* chars) const { | |
| const String& String::trim(String::Trim_kind kind, const char* chars, Charset* source_charset) const { | |
| if(is_empty()) | if(is_empty()) |
| return *this; | return *this; |
| size_t substr_begin, substr_length; | size_t substr_begin, substr_length; |
| Body new_body=body.trim(kind, chars, &substr_begin, &substr_length); | Body new_body=body.trim(kind, chars, &substr_begin, &substr_length, source_charset); |
| if(new_body==body) // we received unchanged pointer, do likewise | if(new_body==body) // we received unchanged pointer, do likewise |
| return *this; | return *this; |
| // new_body differs from body, adjust langs along | // new_body differs from body, adjust langs along |