OSDN Git Service

Initial commit of senna-1.1.2-fast.
[ludiafuncs/senna-1.1.2-fast.git] / lib / snip.c
1 /* Copyright(C) 2004-2005 Brazil
2
3   This library is free software; you can redistribute it and/or
4   modify it under the terms of the GNU Lesser General Public
5   License as published by the Free Software Foundation; either
6   version 2.1 of the License, or (at your option) any later version.
7
8   This library is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY; without even the implied warranty of
10   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11   Lesser General Public License for more details.
12
13   You should have received a copy of the GNU Lesser General Public
14   License along with this library; if not, write to the Free Software
15   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16 */
17 #include "senna_in.h"
18 #include <string.h>
19 #include <stddef.h>
20 #include <assert.h>
21 #include "snip.h"
22 #include "ctx.h"
23
24 #if !defined MAX
25 #define MAX(a, b) ((a) > (b) ? (a) : (b))
26 #endif
27
28 #if !defined MIN
29 #define MIN(a, b) ((a) < (b) ? (a) : (b))
30 #endif
31
32 static int
33 sen_bm_check_euc(const unsigned char *x, const size_t y)
34 {
35   const unsigned char *p;
36   for (p = x + y - 1; p >= x && *p >= 0x80U; p--);
37   return (int) ((x + y - p) & 1);
38 }
39
40 static int
41 sen_bm_check_sjis(const unsigned char *x, const size_t y)
42 {
43   const unsigned char *p;
44   for (p = x + y - 1; p >= x; p--)
45     if ((*p < 0x81U) || (*p > 0x9fU && *p < 0xe0U) || (*p > 0xfcU))
46       break;
47   return (int) ((x + y - p) & 1);
48 }
49
50 /*
51 static void
52 sen_bm_suffixes(const unsigned char *x, size_t m, size_t *suff)
53 {
54   size_t f, g;
55   intptr_t i;
56   f = 0;
57   suff[m - 1] = m;
58   g = m - 1;
59   for (i = m - 2; i >= 0; --i) {
60     if (i > (intptr_t) g && suff[i + m - 1 - f] < i - g)
61       suff[i] = suff[i + m - 1 - f];
62     else {
63       if (i < (intptr_t) g)
64         g = i;
65       f = i;
66       while (g > 0 && x[g] == x[g + m - 1 - f])
67         --g;
68       suff[i] = f - g;
69     }
70   }
71 }
72 */
73
74 static void
75 sen_bm_preBmBc(const unsigned char *x, size_t m, size_t *bmBc)
76 {
77   size_t i;
78   for (i = 0; i < ASIZE; ++i) {
79     bmBc[i] = m;
80   }
81   for (i = 0; i < m - 1; ++i) {
82     bmBc[(unsigned int) x[i]] = m - (i + 1);
83   }
84 }
85
86 #define SEN_BM_COMPARE \
87   if (object->checks[found]) { \
88     size_t offset = cond->start_offset, found_alpha_head = cond->found_alpha_head; \
89     /* calc real offset */\
90     for (i = cond->last_found; i < found; i++) { \
91       if (object->checks[i] > 0) { \
92         found_alpha_head = i; \
93         offset += object->checks[i]; \
94       } \
95     } \
96     /* if real offset is in a character, move it the head of the character */ \
97     if (object->checks[found] < 0) { \
98       offset -= object->checks[found_alpha_head]; \
99       cond->last_found = found_alpha_head; \
100     } else { \
101       cond->last_found = found; \
102     } \
103     if (flags & SEN_SNIP_SKIP_LEADING_SPACES) { \
104       while (offset < object->orig_blen && \
105              (i = sen_isspace(object->orig + offset, object->encoding))) { offset += i; } \
106     } \
107     cond->start_offset = offset; \
108     for (i = cond->last_found; i < found + m; i++) { \
109       if (object->checks[i] > 0) { \
110         offset += object->checks[i]; \
111       } \
112     } \
113     cond->end_offset = offset; \
114     cond->found = found + shift; \
115     cond->found_alpha_head = found_alpha_head; \
116     /* printf("bm: cond:%p found:%zd last_found:%zd st_off:%zd ed_off:%zd\n", cond, cond->found,cond->last_found,cond->start_offset,cond->end_offset); */ \
117     return; \
118   }
119
120 #define SEN_BM_BM_COMPARE \
121 { \
122   if (p[-2] == ck) { \
123     for (i = 3; i <= m && p[-(intptr_t)i] == cp[-(intptr_t)i]; ++i) { \
124     } \
125     if (i > m) { \
126       found = p - y - m; \
127       SEN_BM_COMPARE; \
128     } \
129   } \
130 }
131
132 void
133 sen_bm_tunedbm(snip_cond *cond, sen_nstr *object, int flags)
134 {
135   register unsigned char *limit, ck;
136   register const unsigned char *p, *cp;
137   register size_t *bmBc, delta1, i;
138
139   const unsigned char *x;
140   unsigned char *y;
141   size_t shift, found;
142
143   const size_t n = object->norm_blen, m = cond->keyword->norm_blen;
144
145   y = (unsigned char *) object->norm;
146   if (m == 1) {
147     if (n > cond->found) {
148       shift = 1;
149       p = memchr(y + cond->found, cond->keyword->norm[0], n - cond->found);
150       if (p != NULL) {
151         found = p - y;
152         SEN_BM_COMPARE;
153       }
154     }
155     cond->stopflag = SNIPCOND_STOP;
156     return;
157   }
158
159   x = (unsigned char *) cond->keyword->norm;
160   bmBc = cond->bmBc;
161   shift = cond->shift;
162
163   /* Restart */
164   p = y + m + cond->found;
165   cp = x + m;
166   ck = cp[-2];
167
168   /* 12 means 1(initial offset) + 10 (in loop) + 1 (shift) */
169   if (n - cond->found > 12 * m) {
170     limit = y + n - 11 * m;
171     while (p <= limit) {
172       p += bmBc[p[-1]];
173       if(!(delta1 = bmBc[p[-1]])) {
174         goto check;
175       }
176       p += delta1;
177       p += bmBc[p[-1]];
178       p += bmBc[p[-1]];
179       if(!(delta1 = bmBc[p[-1]])) {
180         goto check;
181       }
182       p += delta1;
183       p += bmBc[p[-1]];
184       p += bmBc[p[-1]];
185       if(!(delta1 = bmBc[p[-1]])) {
186         goto check;
187       }
188       p += delta1;
189       p += bmBc[p[-1]];
190       p += bmBc[p[-1]];
191       continue;
192     check:
193       SEN_BM_BM_COMPARE;
194       p += shift;
195     }
196   }
197   /* limit check + search */
198   limit = y + n;
199   while(p <= limit) {
200     if (!(delta1 = bmBc[p[-1]])) {
201       SEN_BM_BM_COMPARE;
202       p += shift;
203     }
204     p += delta1;
205   }
206   cond->stopflag = SNIPCOND_STOP;
207 }
208
209 static size_t
210 count_mapped_chars(const char *str, const char *end)
211 {
212   const char *p;
213   size_t dl;
214
215   dl = 0;
216   for (p = str; p != end; p++) {
217     switch (*p) {
218     case '<':
219     case '>':
220       dl += 4;                  /* &lt; or &gt; */
221       break;
222     case '&':
223       dl += 5;                  /* &amp; */
224       break;
225     case '"':
226       dl += 6;                  /* &quot; */
227       break;
228     default:
229       dl++;
230       break;
231     }
232   }
233   return dl;
234 }
235
236 sen_rc
237 sen_snip_cond_close(snip_cond *cond)
238 {
239   if (!cond) {
240     return sen_invalid_argument;
241   }
242   if (cond->keyword) {
243     sen_nstr_close(cond->keyword);
244   }
245   return sen_success;
246 }
247
248 sen_rc
249 sen_snip_cond_init(snip_cond *sc, const char *keyword, unsigned int keyword_len,
250                 sen_encoding enc, int flags)
251 {
252   size_t norm_blen;
253   memset(sc, 0, sizeof(snip_cond));
254   if (flags & SEN_SNIP_NORMALIZE) {
255     if (!(sc->keyword = sen_nstr_open(keyword, keyword_len,
256                                       enc, SEN_STR_REMOVEBLANK))) {
257       SEN_LOG(sen_log_alert, "sen_nstr_open on snip_cond_init failed !");
258       return sen_memory_exhausted;
259     }
260   } else {
261     if (!(sc->keyword = sen_fakenstr_open(keyword, keyword_len,
262                                           enc, SEN_STR_REMOVEBLANK))) {
263       SEN_LOG(sen_log_alert, "sen_fakenstr_open on snip_cond_init failed !");
264       return sen_memory_exhausted;
265     }
266   }
267   norm_blen = sc->keyword->norm_blen; /* byte length, not cond->keyword->length */
268   if (!norm_blen) {
269     sen_snip_cond_close(sc);
270     return sen_invalid_argument;
271   }
272   if (norm_blen != 1) {
273     sen_bm_preBmBc((unsigned char *)sc->keyword->norm, norm_blen, sc->bmBc);
274     sc->shift = sc->bmBc[(unsigned char)sc->keyword->norm[norm_blen - 1]];
275     sc->bmBc[(unsigned char)sc->keyword->norm[norm_blen - 1]] = 0;
276   }
277   return sen_success;
278 }
279
280 void
281 sen_snip_cond_reinit(snip_cond *cond)
282 {
283   cond->found = 0;
284   cond->last_found = 0;
285   cond->start_offset = 0;
286   cond->end_offset = 0;
287
288   cond->count = 0;
289   cond->stopflag = SNIPCOND_NONSTOP;
290 }
291
292 sen_rc
293 sen_snip_add_cond(sen_snip *snip,
294                   const char *keyword, unsigned int keyword_len,
295                   const char *opentag, unsigned int opentag_len,
296                   const char *closetag, unsigned int closetag_len)
297 {
298   sen_rc rc;
299   snip_cond *cond;
300   sen_ctx *ctx = &sen_gctx; /* todo : replace it with the local ctx */
301
302   if (!snip || !keyword || !keyword_len || snip->cond_len >= MAX_SNIP_COND_COUNT) {
303     return sen_invalid_argument;
304   }
305   cond = snip->cond + snip->cond_len;
306   if ((rc = sen_snip_cond_init(cond, keyword, keyword_len,
307                                snip->encoding, snip->flags))) {
308     return rc;
309   }
310   if (cond->keyword->norm_blen > snip->width) {
311     sen_snip_cond_close(cond);
312     return sen_invalid_argument;
313   }
314   if (opentag) {
315     if (snip->flags & SEN_SNIP_COPY_TAG) {
316       char *t = SEN_MALLOC(opentag_len + 1);
317       if (!t) {
318         sen_snip_cond_close(cond);
319         return sen_memory_exhausted;
320       }
321       memcpy(t, opentag, opentag_len);
322       t[opentag_len]= '\0'; /* not required, but for ql use */
323       cond->opentag = t;
324     } else {
325       cond->opentag = opentag;
326     }
327     cond->opentag_len = opentag_len;
328   } else {
329     cond->opentag = snip->defaultopentag;
330     cond->opentag_len = snip->defaultopentag_len;
331   }
332   if (closetag) {
333     if (snip->flags & SEN_SNIP_COPY_TAG) {
334       char *t = SEN_MALLOC(closetag_len + 1);
335       if (!t) { return sen_memory_exhausted; }
336       memcpy(t, closetag, closetag_len);
337       t[closetag_len]= '\0'; /* not required, but for ql use */
338       cond->closetag = t;
339     } else {
340       cond->closetag = closetag;
341     }
342     cond->closetag_len = closetag_len;
343   } else {
344     cond->closetag = snip->defaultclosetag;
345     cond->closetag_len = snip->defaultclosetag_len;
346   }
347   snip->cond_len++;
348   return sen_success;
349 }
350
351 static size_t
352 sen_snip_find_firstbyte(const char *string, sen_encoding encoding, size_t offset,
353                         size_t doffset)
354 {
355   switch (encoding) {
356   case sen_enc_euc_jp:
357     while (!(sen_bm_check_euc((unsigned char *) string, offset)))
358       offset += doffset;
359     break;
360   case sen_enc_sjis:
361     if (!(sen_bm_check_sjis((unsigned char *) string, offset)))
362       offset += doffset;
363     break;
364   case sen_enc_utf8:
365     while (string[offset] <= (char)0xc0)
366       offset += doffset;
367     break;
368   default:
369     break;
370   }
371   return offset;
372 }
373
374 sen_snip *
375 sen_snip_open(sen_encoding encoding, int flags, unsigned int width,
376               unsigned int max_results,
377               const char *defaultopentag, unsigned int defaultopentag_len,
378               const char *defaultclosetag, unsigned int defaultclosetag_len,
379               sen_snip_mapping *mapping)
380 {
381   sen_ctx *ctx = &sen_gctx; /* todo : replace it with the local ctx */
382   sen_snip *ret = NULL;
383   if (!(ret = SEN_MALLOC(sizeof(sen_snip)))) {
384     SEN_LOG(sen_log_alert, "sen_snip allocation failed on sen_snip_open");
385     return NULL;
386   }
387   if (max_results > MAX_SNIP_RESULT_COUNT || max_results == 0) {
388     SEN_LOG(sen_log_warning, "max_results is invalid on sen_snip_open");
389     return NULL;
390   }
391   ret->encoding = encoding;
392   ret->flags = flags;
393   ret->width = width;
394   ret->max_results = max_results;
395   if (flags & SEN_SNIP_COPY_TAG) {
396     char *t;
397     t = SEN_MALLOC(defaultopentag_len + 1);
398     if (!t) {
399       SEN_FREE(ret);
400       return NULL;
401     }
402     memcpy(t, defaultopentag, defaultopentag_len);
403     t[defaultopentag_len]= '\0'; /* not required, but for ql use */
404     ret->defaultopentag = t;
405
406     t = SEN_MALLOC(defaultclosetag_len + 1);
407     if (!t) {
408       SEN_FREE((void *)ret->defaultopentag);
409       SEN_FREE(ret);
410       return NULL;
411     }
412     memcpy(t, defaultclosetag, defaultclosetag_len);
413     t[defaultclosetag_len]= '\0'; /* not required, but for ql use */
414     ret->defaultclosetag = t;
415   } else {
416     ret->defaultopentag = defaultopentag;
417     ret->defaultclosetag = defaultclosetag;
418   }
419   ret->defaultopentag_len = defaultopentag_len;
420   ret->defaultclosetag_len = defaultclosetag_len;
421   ret->cond_len = 0;
422   ret->mapping = mapping;
423   ret->nstr = NULL;
424   ret->tag_count = 0;
425   ret->snip_count = 0;
426
427   return ret;
428 }
429
430 static sen_rc
431 exec_clean(sen_snip *snip)
432 {
433   snip_cond *cond, *cond_end;
434   if (snip->nstr) {
435     sen_nstr_close(snip->nstr);
436     snip->nstr = NULL;
437   }
438   snip->tag_count = 0;
439   snip->snip_count = 0;
440   for (cond = snip->cond, cond_end = cond + snip->cond_len;
441        cond < cond_end; cond++) {
442     sen_snip_cond_reinit(cond);
443   }
444   return sen_success;
445 }
446
447 sen_rc
448 sen_snip_close(sen_snip *snip)
449 {
450   sen_ctx *ctx = &sen_gctx; /* todo : replace it with the local ctx */
451   snip_cond *cond, *cond_end;
452   if (!snip) { return sen_invalid_argument; }
453   if (snip->flags & SEN_SNIP_COPY_TAG) {
454     int i;
455     snip_cond *sc;
456     const char *dot = snip->defaultopentag, *dct = snip->defaultclosetag;
457     for (i = snip->cond_len, sc = snip->cond; i; i--, sc++) {
458       if (sc->opentag != dot) { SEN_FREE((void *)sc->opentag); }
459       if (sc->closetag != dct) { SEN_FREE((void *)sc->closetag); }
460     }
461     if (dot) { SEN_FREE((void *)dot); }
462     if (dct) { SEN_FREE((void *)dct); }
463   }
464   if (snip->nstr) {
465     sen_nstr_close(snip->nstr);
466   }
467   for (cond = snip->cond, cond_end = cond + snip->cond_len;
468        cond < cond_end; cond++) {
469     sen_snip_cond_close(cond);
470   }
471   SEN_FREE(snip);
472   return sen_success;
473 }
474
475 sen_rc
476 sen_snip_exec(sen_snip *snip, const char *string, unsigned int string_len,
477               unsigned int *nresults, unsigned int *max_tagged_len)
478 {
479   size_t i;
480   if (!snip || !string) {
481     return sen_invalid_argument;
482   }
483   exec_clean(snip);
484   *nresults = 0;
485   if (snip->flags & SEN_SNIP_NORMALIZE) {
486     snip->nstr =
487       sen_nstr_open(string, string_len, snip->encoding,
488                     SEN_STR_WITH_CHECKS | SEN_STR_REMOVEBLANK);
489   } else {
490     snip->nstr =
491       sen_fakenstr_open(string, string_len, snip->encoding,
492                         SEN_STR_WITH_CHECKS | SEN_STR_REMOVEBLANK);
493   }
494   if (!snip->nstr) {
495     exec_clean(snip);
496     SEN_LOG(sen_log_alert, "sen_nstr_open on sen_snip_exec failed !");
497     return sen_memory_exhausted;
498   }
499   for (i = 0; i < snip->cond_len; i++) {
500     sen_bm_tunedbm(snip->cond + i, snip->nstr, snip->flags);
501   }
502
503   {
504     _snip_tag_result *tag_result = snip->tag_result;
505     _snip_result *snip_result = snip->snip_result;
506     size_t last_end_offset = 0, last_last_end_offset = 0;
507     unsigned int unfound_cond_count = snip->cond_len;
508
509     *max_tagged_len = 0;
510     while (1) {
511       size_t tagged_len = 0, last_tag_end = 0;
512       int_least8_t all_stop = 1, found_cond = 0;
513       snip_result->tag_count = 0;
514
515       while (1) {
516         size_t min_start_offset = (size_t) -1;
517         size_t max_end_offset = 0;
518         snip_cond *cond = NULL;
519
520         /* get condition which have minimum offset and is not stopped */
521         for (i = 0; i < snip->cond_len; i++) {
522           if (snip->cond[i].stopflag == SNIPCOND_NONSTOP &&
523               (min_start_offset > snip->cond[i].start_offset ||
524                (min_start_offset == snip->cond[i].start_offset &&
525                 max_end_offset < snip->cond[i].end_offset))) {
526             min_start_offset = snip->cond[i].start_offset;
527             max_end_offset = snip->cond[i].end_offset;
528             cond = &snip->cond[i];
529           }
530         }
531         if (!cond) {
532           break;
533         }
534         /* check whether condtion is the first condition in snippet */
535         if (snip_result->tag_count == 0) {
536           /* skip condition if the number of rest snippet field is smaller than */
537           /* the number of unfound keywords. */
538           if (snip->max_results - *nresults <= unfound_cond_count && cond->count > 0) {
539             int_least8_t exclude_other_cond = 1;
540             for (i = 0; i < snip->cond_len; i++) {
541               if ((snip->cond + i) != cond
542                   && snip->cond[i].end_offset <= cond->start_offset + snip->width
543                   && snip->cond[i].count == 0) {
544                 exclude_other_cond = 0;
545               }
546             }
547             if (exclude_other_cond) {
548               sen_bm_tunedbm(cond, snip->nstr, snip->flags);
549               continue;
550             }
551           }
552           snip_result->start_offset = cond->start_offset;
553           snip_result->first_tag_result_idx = snip->tag_count;
554         } else {
555           if (cond->start_offset >= snip_result->start_offset + snip->width) {
556             break;
557           }
558           /* check nesting to make valid HTML */
559           /* ToDo: allow <test><te>te</te><st>st</st></test> */
560           if (cond->start_offset < last_tag_end) {
561             sen_bm_tunedbm(cond, snip->nstr, snip->flags);
562             continue;
563           }
564         }
565         if (cond->end_offset > snip_result->start_offset + snip->width) {
566           /* If a keyword gets across a snippet, */
567           /* it was skipped and never to be tagged. */
568           cond->stopflag = SNIPCOND_ACROSS;
569           sen_bm_tunedbm(cond, snip->nstr, snip->flags);
570         } else {
571           found_cond = 1;
572           if (cond->count == 0) {
573             unfound_cond_count--;
574           }
575           cond->count++;
576           last_end_offset = cond->end_offset;
577
578           tag_result->cond = cond;
579           tag_result->start_offset = cond->start_offset;
580           tag_result->end_offset = last_tag_end = cond->end_offset;
581
582           snip_result->tag_count++;
583           tag_result++;
584           tagged_len += cond->opentag_len + cond->closetag_len;
585           if (++snip->tag_count >= MAX_SNIP_TAG_COUNT) {
586             break;
587           }
588           sen_bm_tunedbm(cond, snip->nstr, snip->flags);
589         }
590       }
591       if (!found_cond) {
592         break;
593       }
594       if (snip_result->start_offset + last_end_offset < snip->width) {
595         snip_result->start_offset = 0;
596       } else {
597         snip_result->start_offset =
598           MAX(MIN
599               ((snip_result->start_offset + last_end_offset - snip->width) / 2,
600                string_len - snip->width), last_last_end_offset);
601       }
602       snip_result->start_offset =
603         sen_snip_find_firstbyte(string, snip->encoding, snip_result->start_offset, 1);
604
605       snip_result->end_offset = snip_result->start_offset + snip->width;
606       if (snip_result->end_offset < string_len) {
607         snip_result->end_offset =
608           sen_snip_find_firstbyte(string, snip->encoding, snip_result->end_offset, -1);
609       } else {
610         snip_result->end_offset = string_len;
611       }
612       last_last_end_offset = snip_result->end_offset;
613
614       tagged_len +=
615         count_mapped_chars(&string[snip_result->start_offset],
616                            &string[snip_result->end_offset]) + 1;
617
618       *max_tagged_len = MAX(*max_tagged_len, tagged_len);
619
620       snip_result->last_tag_result_idx = snip->tag_count - 1;
621       (*nresults)++;
622       snip_result++;
623
624       if (*nresults == snip->max_results || snip->tag_count == MAX_SNIP_TAG_COUNT) {
625         break;
626       }
627       for (i = 0; i < snip->cond_len; i++) {
628         if (snip->cond[i].stopflag != SNIPCOND_STOP) {
629           all_stop = 0;
630           snip->cond[i].stopflag = SNIPCOND_NONSTOP;
631         }
632       }
633       if (all_stop) {
634         break;
635       }
636     }
637   }
638   snip->snip_count = *nresults;
639   snip->string = string;
640
641   snip->max_tagged_len = *max_tagged_len;
642
643   return sen_success;
644 }
645
646 sen_rc
647 sen_snip_get_result(sen_snip *snip, const unsigned int index, char *result, unsigned int *result_len)
648 {
649   char *p;
650   size_t i, j, k;
651   _snip_result *sres = &snip->snip_result[index];
652
653   if (snip->snip_count <= index || !snip->nstr) {
654     return sen_invalid_argument;
655   }
656
657   assert(snip->snip_count != 0 && snip->tag_count != 0);
658
659   j = sres->first_tag_result_idx;
660   for (p = result, i = sres->start_offset; i < sres->end_offset; i++) {
661     for (; j <= sres->last_tag_result_idx && snip->tag_result[j].start_offset == i; j++) {
662       if (snip->tag_result[j].end_offset > sres->end_offset) {
663         continue;
664       }
665       memcpy(p, snip->tag_result[j].cond->opentag, snip->tag_result[j].cond->opentag_len);
666       p += snip->tag_result[j].cond->opentag_len;
667     }
668
669     if (snip->mapping == (sen_snip_mapping *) -1) {
670       switch (snip->string[i]) {
671       case '<':
672         *p++ = '&';
673         *p++ = 'l';
674         *p++ = 't';
675         *p++ = ';';
676         break;
677       case '>':
678         *p++ = '&';
679         *p++ = 'g';
680         *p++ = 't';
681         *p++ = ';';
682         break;
683       case '&':
684         *p++ = '&';
685         *p++ = 'a';
686         *p++ = 'm';
687         *p++ = 'p';
688         *p++ = ';';
689         break;
690       case '"':
691         *p++ = '&';
692         *p++ = 'q';
693         *p++ = 'u';
694         *p++ = 'o';
695         *p++ = 't';
696         *p++ = ';';
697         break;
698       default:
699         *p++ = snip->string[i];
700         break;
701       }
702     } else {
703       *p++ = snip->string[i];
704     }
705
706     for (k = sres->last_tag_result_idx;
707          snip->tag_result[k].end_offset <= sres->end_offset; k--) {
708       /* TODO: avoid all loop */
709       if (snip->tag_result[k].end_offset == i + 1) {
710         memcpy(p, snip->tag_result[k].cond->closetag,
711                snip->tag_result[k].cond->closetag_len);
712         p += snip->tag_result[k].cond->closetag_len;
713       }
714       if (k <= sres->first_tag_result_idx) {
715         break;
716       }
717     };
718   }
719   *p = '\0';
720
721   if(result_len) { *result_len = (unsigned int)(p - result); }
722   assert((unsigned int)(p - result) <= snip->max_tagged_len);
723
724   return sen_success;
725 }