]>
diplodocus.org Git - nmh/blob - sbr/fmt_scan.c
3 * fmt_scan.c -- format string interpretation
5 * This code is Copyright (c) 2002, by the authors of nmh. See the
6 * COPYRIGHT file in the root directory of the nmh distribution for
7 * complete copyright information.
9 * This is the engine that processes the format instructions created by
10 * fmt_compile (found in fmt_compile.c).
14 #include <h/addrsbr.h>
15 #include <h/fmt_scan.h>
17 #include <h/fmt_compile.h>
19 #ifdef HAVE_SYS_TIME_H
20 # include <sys/time.h>
23 #ifdef MULTIBYTE_SUPPORT
28 extern int fmt_norm
; /* defined in sbr/fmt_def.c = AD_NAME */
29 struct mailname fmt_mnull
= { NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, 0, 0, 0, 0,
35 static int match (char *, char *);
36 static char *get_x400_friendly (char *, char *, int);
37 static int get_x400_comp (char *, char *, char *, int);
41 * test if string "sub" appears anywhere in
42 * string "str" (case insensitive).
46 match (char *str
, char *sub
)
53 c1
= (isascii((unsigned char) c1
) && isalpha((unsigned char) c1
) &&
54 isupper((unsigned char) c1
)) ? tolower((unsigned char) c1
) : c1
;
55 while ((c2
= *str
++) && c1
!= ((isascii((unsigned char) c2
) &&
56 isalpha((unsigned char) c2
) &&
57 isupper((unsigned char) c2
)) ?
58 tolower((unsigned char) c2
) : c2
))
62 s1
= sub
+ 1; s2
= str
;
63 while ((c1
= *s1
++) && ((isascii((unsigned char) c1
) &&
64 isalpha((unsigned char) c1
) &&
65 isupper((unsigned char) c1
)) ?
67 ((isascii((unsigned char) (c2
=*s2
++)) &&
68 isalpha((unsigned char) c2
) &&
69 isupper((unsigned char) c2
)) ?
70 tolower((unsigned char) c2
) : c2
))
77 while ((c2
= *str
++) && (c1
| 040) != (c2
| 040))
81 s1
= sub
+ 1; s2
= str
;
82 while ((c1
= *s1
++) && (c1
| 040) == (*s2
++ | 040))
92 * copy a number to the destination subject to a maximum width
95 cpnumber(char **dest
, int num
, unsigned int wid
, char fill
, size_t n
) {
108 *--sp
= (i
% 10) + '0';
110 } while (i
> 0 && sp
> cp
);
113 else if ((num
) < 0 && sp
> cp
)
123 * copy string from str to dest padding with the fill character to a size
124 * of wid characters. if wid is negative, the string is right aligned
125 * no more than n bytes are copied
128 cptrimmed(char **dest
, char **ep
, char *str
, unsigned int wid
, char fill
,
130 int remaining
; /* remaining output width available */
132 int end
; /* number of input bytes remaining in str */
133 #ifdef MULTIBYTE_SUPPORT
134 int char_len
; /* bytes in current character */
139 char *sp
; /* current position in source string */
140 char *cp
= *dest
; /* current position in destination string */
145 if ((remaining
= (wid
)) < 0) {
146 remaining
= -remaining
;
150 #ifdef MULTIBYTE_SUPPORT
151 mbtowc(NULL
, NULL
, 0); /* reset shift state */
154 while (*sp
&& remaining
> 0 && end
> 0) {
155 #ifdef MULTIBYTE_SUPPORT
156 char_len
= mbtowc(&wide_char
, sp
, end
);
159 * See the relevant comments in cpstripped() to explain what's
160 * going on here; we want to handle the case where we get
161 * characters that mbtowc() cannot handle
166 char_len
= mbtowc(&wide_char
, altstr
, 1);
172 w
= wcwidth(wide_char
);
175 * Multibyte characters can have a variable number of column
176 * widths, so use the column width to bump the end pointer when
179 if (char_len
> 1 && epmax
- *ep
>= char_len
- w
) {
188 if (iswcntrl(wide_char
) || iswspace(wide_char
)) {
193 /* isnctrl(), etc., take an int argument. Cygwin's ctype.h
194 intentionally warns if they are passed a char. */
195 c
= (unsigned char) *sp
;
196 if (iscntrl(c
) || isspace(c
)) {
209 #ifdef MULTIBYTE_SUPPORT
210 if (w
>= 0 && remaining
>= w
) {
211 strncpy(cp
, altstr
? altstr
: sp
, char_len
);
226 if (cp
+ remaining
> *ep
)
227 remaining
= *ep
- cp
;
228 endfield
= cp
+ remaining
;
230 /* copy string to the right */
231 while (--cp
>= *dest
)
232 *(cp
+ remaining
) = *cp
;
233 /* add padding at the beginning */
235 for (c
=remaining
; c
>0; c
--)
240 /* pad remaining space */
241 while (remaining
-- > 0 && cp
< *ep
)
248 cpstripped (char **dest
, char **end
, char *max
, char *str
)
250 int prevCtrl
= 1; /* This is 1 so we strip out leading spaces */
252 #ifdef MULTIBYTE_SUPPORT
256 #endif /* MULTIBYTE_SUPPORT */
263 #ifdef MULTIBYTE_SUPPORT
264 mbtowc(NULL
, NULL
, 0); /* Reset shift state */
265 #endif /* MULTIBYTE_SUPPORT */
268 * Process each character at a time; if we have multibyte support
269 * then deal with that here.
272 while (*str
!= '\0' && len
> 0 && *dest
< *end
) {
273 #ifdef MULTIBYTE_SUPPORT
274 char_len
= mbtowc(&wide_char
, str
, len
);
275 w
= wcwidth(wide_char
);
278 * Account for multibyte characters, and increment the end pointer
279 * by the number of "extra" bytes in this character. That's the
280 * character length (char_len) minus the column width (w).
282 if (char_len
> 1 && max
- *end
>= char_len
- w
) {
283 *end
+= char_len
- w
;
287 * If mbrtowc() failed, then we have a character that isn't valid
288 * in the current encoding. Replace it with a '?'. We do that by
289 * setting the alstr variable to the value of the replacement string;
290 * altstr is used below when the bytes are copied into the output
296 char_len
= mbtowc(&wide_char
, altstr
, 1);
299 if (char_len
<= 0 || *dest
+ char_len
> *end
)
304 if (iswcntrl(wide_char
) || iswspace(wide_char
)) {
306 #else /* MULTIBYTE_SUPPORT */
307 int c
= (unsigned char) *str
;
309 if (iscntrl(c
) || isspace(c
)) {
311 #endif /* MULTIBYTE_SUPPORT */
322 #ifdef MULTIBYTE_SUPPORT
323 memcpy(*dest
, altstr
? altstr
: str
, char_len
);
327 #else /* MULTIBYE_SUPPORT */
329 #endif /* MULTIBYTE_SUPPORT */
333 static char *lmonth
[] = { "January", "February","March", "April",
334 "May", "June", "July", "August",
335 "September","October", "November","December" };
338 get_x400_friendly (char *mbox
, char *buffer
, int buffer_len
)
340 char given
[BUFSIZ
], surname
[BUFSIZ
];
349 if (get_x400_comp (mbox
, "/PN=", buffer
, buffer_len
)) {
350 for (mbox
= buffer
; (mbox
= strchr(mbox
, '.')); )
356 if (!get_x400_comp (mbox
, "/S=", surname
, sizeof(surname
)))
359 if (get_x400_comp (mbox
, "/G=", given
, sizeof(given
)))
360 snprintf (buffer
, buffer_len
, "%s %s", given
, surname
);
362 snprintf (buffer
, buffer_len
, "%s", surname
);
368 get_x400_comp (char *mbox
, char *key
, char *buffer
, int buffer_len
)
373 if ((idx
= stringdex (key
, mbox
)) < 0
374 || !(cp
= strchr(mbox
+= idx
+ strlen (key
), '/')))
377 snprintf (buffer
, buffer_len
, "%*.*s", (int)(cp
- mbox
), (int)(cp
- mbox
), mbox
);
382 fmt_scan (struct format
*format
, char *scanl
, size_t max
, int width
, int *dat
,
383 struct fmt_callbacks
*callbacks
)
386 char *savestr
= NULL
, *str
= NULL
;
387 char buffer
[BUFSIZ
], buffer2
[BUFSIZ
];
396 /* ep keeps track of displayed characters. They're limited by width.
397 The total number of characters, cp - scanl + 1 (for trailing NULL),
398 includes invisible control characters and is limited by max. */
400 ep
= scanl
+ (width
<= (int) max
? width
: (int) max
) - 1;
402 for (fmt
= format
; fmt
->f_type
!= FT_DONE
; fmt
++)
403 switch (fmt
->f_type
) {
406 fmt
->f_comp
->c_flags
&= ~CF_PARSED
;
411 case FT_LS_DECODECOMP
:
413 * Trim these components of any newlines.
415 * But don't trim the "body" and "text" components.
420 if (! (comp
->c_flags
& CF_TRIMMED
) && comp
->c_text
&&
421 (i
= strlen(comp
->c_text
)) > 0) {
422 if (comp
->c_text
[i
- 1] == '\n' &&
423 strcmp(comp
->c_name
, "body") != 0 &&
424 strcmp(comp
->c_name
, "text") != 0)
425 comp
->c_text
[i
- 1] = '\0';
426 comp
->c_flags
|= CF_TRIMMED
;
434 switch (fmt
->f_type
) {
437 cpstripped (&cp
, &ep
, scanl
+ max
- 1, fmt
->f_comp
->c_text
);
440 cptrimmed (&cp
, &ep
, fmt
->f_comp
->c_text
, fmt
->f_width
, fmt
->f_fill
,
446 while( (c
= *sp
++) && cp
< ep
)
455 ljust
++; /* XXX should do something with this */
457 while( (c
= *sp
++) && --i
>= 0 && cp
< ep
)
459 while( --i
>= 0 && cp
< ep
)
464 cpstripped (&cp
, &ep
, scanl
+ max
- 1, str
);
467 cptrimmed (&cp
, &ep
, str
, fmt
->f_width
, fmt
->f_fill
,
472 while ((c
= *sp
++) && cp
< ep
)
476 size_t len
= strlen (str
);
478 /* Don't want to emit part of an escape sequence. So if
479 there isn't enough room in the buffer for the entire
480 string, skip it completely. */
481 if (cp
- scanl
+ len
+ 1 < max
) {
482 for (sp
= str
; *sp
; *cp
++ = *sp
++) continue;
484 /* This string doesn't count against the width. So
485 increase ep the same amount as cp, only if the
486 scan buffer will always be large enough. */
487 if (ep
- scanl
+ len
+ 1 < max
) {
495 adios (NULL
, "internal error (FT_STRFW)");
498 n
= snprintf(cp
, ep
- cp
+ 1, "%d", value
);
507 cpnumber (&cp
, value
, fmt
->f_width
, fmt
->f_fill
, ep
- cp
);
515 if (callbacks
&& callbacks
->trace_func
)
516 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
521 if (!(value
= (str
&& *str
))) {
528 if (!(value
= (str
== NULL
|| *str
== 0))) {
535 if (value
!= fmt
->f_value
) {
542 if (value
== fmt
->f_value
) {
549 if (value
<= fmt
->f_value
) {
556 if (!(value
= (str
&& match (str
, fmt
->f_text
)))) {
564 value
= match (str
, fmt
->f_text
);
570 if (!(value
= (str
&& uprf (str
, fmt
->f_text
)))) {
577 value
= uprf (str
, fmt
->f_text
);
581 value
= (str
!= NULL
&& *str
!= 0);
585 value
= (str
== NULL
|| *str
== 0);
589 value
= (fmt
->f_value
== value
);
593 value
= (fmt
->f_value
!= value
);
597 value
= (fmt
->f_value
> value
);
608 str
= fmt
->f_comp
->c_text
;
614 if (!(str
= getenv (fmt
->f_text
)))
618 if (!(str
= context_find (fmt
->f_text
)))
622 case FT_LS_DECODECOMP
:
623 if (decode_rfc2047(fmt
->f_comp
->c_text
, buffer2
, sizeof(buffer2
)))
626 str
= fmt
->f_comp
->c_text
;
630 if (str
&& decode_rfc2047(str
, buffer2
, sizeof(buffer2
)))
638 strncpy(buffer
, str
, sizeof(buffer
));
639 buffer
[sizeof(buffer
)-1] = '\0';
641 while (isspace((unsigned char) *str
))
644 if ((i
= fmt
->f_width
) < 0) {
649 if (!ljust
&& i
> 0 && (int) strlen(str
) > i
)
652 xp
+= strlen(str
) - 1;
653 while (xp
> str
&& isspace((unsigned char) *xp
))
655 if (ljust
&& i
> 0 && (int) strlen(str
) > i
)
656 str
+= strlen(str
) - i
;
661 value
= (fmt
->f_comp
->c_flags
& CF_TRUE
) != 0;
664 value
= (comp
= fmt
->f_comp
)->c_text
? atoi(comp
->c_text
) : 0;
667 value
= fmt
->f_value
;
670 value
= dat
[fmt
->f_value
];
678 case FT_LV_CHAR_LEFT
:
679 value
= width
- (cp
- scanl
);
682 value
+= fmt
->f_value
;
685 value
= fmt
->f_value
- value
;
689 value
= value
/ fmt
->f_value
;
695 value
= value
% fmt
->f_value
;
704 value
= fmt
->f_comp
->c_tws
->tw_sec
;
707 value
= fmt
->f_comp
->c_tws
->tw_min
;
710 value
= fmt
->f_comp
->c_tws
->tw_hour
;
713 value
= fmt
->f_comp
->c_tws
->tw_mday
;
716 value
= fmt
->f_comp
->c_tws
->tw_mon
+ 1;
719 str
= tw_moty
[fmt
->f_comp
->c_tws
->tw_mon
];
722 str
= lmonth
[fmt
->f_comp
->c_tws
->tw_mon
];
725 str
= dtwszone (fmt
->f_comp
->c_tws
);
728 value
= fmt
->f_comp
->c_tws
->tw_year
;
731 if (!(((tws
= fmt
->f_comp
->c_tws
)->tw_flags
) & (TW_SEXP
|TW_SIMP
)))
733 value
= tws
->tw_wday
;
736 if (!(((tws
= fmt
->f_comp
->c_tws
)->tw_flags
) & (TW_SEXP
|TW_SIMP
)))
738 str
= tw_dotw
[tws
->tw_wday
];
741 if (!(((tws
= fmt
->f_comp
->c_tws
)->tw_flags
) & (TW_SEXP
|TW_SIMP
)))
743 str
= tw_ldotw
[tws
->tw_wday
];
746 value
= fmt
->f_comp
->c_tws
->tw_yday
;
749 value
= fmt
->f_comp
->c_tws
->tw_zone
;
752 if ((value
= fmt
->f_comp
->c_tws
->tw_clock
) == 0)
753 value
= dmktime(fmt
->f_comp
->c_tws
);
756 if ((value
= fmt
->f_comp
->c_tws
->tw_clock
) == 0)
757 value
= dmktime(fmt
->f_comp
->c_tws
);
758 value
= time((time_t *) 0) - value
;
761 if (!(((tws
= fmt
->f_comp
->c_tws
)->tw_flags
) & (TW_SEXP
|TW_SIMP
)))
763 switch (fmt
->f_comp
->c_tws
->tw_flags
& TW_SDAY
) {
772 if ((fmt
->f_comp
->c_tws
->tw_flags
& TW_SZONE
) == TW_SZEXP
)
778 value
= fmt
->f_comp
->c_tws
->tw_flags
& TW_DST
;
781 str
= dasctime (fmt
->f_comp
->c_tws
, TW_ZONE
);
784 str
= dasctime (fmt
->f_comp
->c_tws
, TW_NULL
);
788 str
= fmt
->f_comp
->c_mn
->m_pers
;
791 str
= fmt
->f_comp
->c_mn
->m_mbox
;
794 str
= fmt
->f_comp
->c_mn
->m_host
;
797 str
= fmt
->f_comp
->c_mn
->m_path
;
800 str
= fmt
->f_comp
->c_mn
->m_gname
;
803 str
= fmt
->f_comp
->c_mn
->m_note
;
806 str
= adrformat( fmt
->f_comp
->c_mn
);
809 value
= fmt
->f_comp
->c_mn
->m_type
;
812 value
= fmt
->f_comp
->c_mn
->m_ingrp
;
815 value
= fmt
->f_comp
->c_mn
->m_nohost
;
819 if ((mn
= fmt
->f_comp
->c_mn
) == &fmt_mnull
) {
820 str
= fmt
->f_comp
->c_text
;
823 if (fmt
->f_type
== FT_LS_ADDR
)
825 if ((str
= mn
->m_pers
) == NULL
) {
826 if ((str
= mn
->m_note
)) {
827 strncpy (buffer
, str
, sizeof(buffer
));
828 buffer
[sizeof(buffer
)-1] = '\0';
832 sp
= str
+ strlen(str
) - 1;
841 } else if (!(str
= get_x400_friendly (mn
->m_mbox
,
842 buffer
, sizeof(buffer
)))) {
844 switch (mn
->m_type
) {
849 snprintf (buffer
, sizeof(buffer
), "%s!%s",
850 mn
->m_host
, mn
->m_mbox
);
855 snprintf (buffer
, sizeof(buffer
), "%s@%s",
856 mn
->m_mbox
, mn
->m_host
);
868 /* UNQUOTEs RFC-2822 quoted-string and quoted-pair */
872 strncpy(buffer
, str
, sizeof(buffer
));
873 /* strncpy doesn't NUL-terminate if it fills the buffer */
874 buffer
[sizeof(buffer
)-1] = '\0';
877 /* we will parse from buffer to buffer2 */
878 n
= 0; /* n is the input position in str */
879 m
= 0; /* m is the ouput position in buffer2 */
881 while ( str
[n
] != '\0') {
886 buffer2
[m
++] = str
[n
++];
892 buffer2
[m
++] = str
[n
++];
903 if ((t
= comp
->c_tws
->tw_clock
) == 0)
904 t
= dmktime(comp
->c_tws
);
905 tws
= dlocaltime(&t
);
911 if ((t
= comp
->c_tws
->tw_clock
) == 0)
912 t
= dmktime(comp
->c_tws
);
919 if (comp
->c_flags
& CF_PARSED
)
921 if ((sp
= comp
->c_text
) && (tws
= dparsetime(sp
))) {
923 comp
->c_flags
&= ~CF_TRUE
;
924 } else if ((comp
->c_flags
& CF_DATEFAB
) == 0) {
925 memset ((char *) comp
->c_tws
, 0, sizeof *comp
->c_tws
);
926 comp
->c_flags
= CF_TRUE
;
928 comp
->c_flags
|= CF_PARSED
;
932 /* hook for custom address list formatting (see replsbr.c) */
933 if (callbacks
&& callbacks
->formataddr
)
934 str
= callbacks
->formataddr (savestr
, str
);
936 str
= formataddr (savestr
, str
);
940 /* The same as formataddr, but doesn't do duplicate suppression */
941 if (callbacks
&& callbacks
->concataddr
)
942 str
= callbacks
->concataddr (savestr
, str
);
944 str
= concataddr (savestr
, str
);
948 /* output the str register as an address component,
949 * splitting it into multiple lines if necessary. The
950 * value reg. contains the max line length. The lit.
951 * field may contain a string to prepend to the result
956 int indent
, wid
, len
;
962 indent
= strlen (sp
);
965 adios(NULL
, "putaddr -- num register (%d) must be greater "
966 "than label width (%d)", value
, indent
);
968 while( (c
= (unsigned char) *sp
++) && cp
< ep
)
971 /* try to break at a comma; failing that, break at a
974 lastb
= 0; sp
= lp
+ wid
;
975 while (sp
> lp
&& (c
= (unsigned char) *--sp
) != ',') {
976 if (! lastb
&& isspace(c
))
980 if (! (sp
= lastb
)) {
982 while (*sp
&& *sp
!= ',' &&
983 !isspace((unsigned char) *sp
))
990 while (cp
< ep
&& lp
<= sp
)
992 while (isspace((unsigned char) *lp
))
997 for (i
=indent
; cp
< ep
&& i
> 0; i
--)
1001 cpstripped (&cp
, &ep
, scanl
+ max
- 1, lp
);
1007 if (comp
->c_flags
& CF_PARSED
)
1009 if (comp
->c_mn
!= &fmt_mnull
)
1010 mnfree (comp
->c_mn
);
1011 if ((sp
= comp
->c_text
) && (sp
= getname(sp
)) &&
1012 (mn
= getm (sp
, NULL
, 0, fmt_norm
, NULL
))) {
1016 comp
->c_flags
|= CF_PARSED
;
1018 while (getname("")) /* XXX */
1020 comp
->c_mn
= &fmt_mnull
;
1026 * if there's no component, we say true. Otherwise we
1027 * say "true" only if we can parse the address and it
1028 * matches one of our addresses.
1031 if (comp
->c_mn
!= &fmt_mnull
)
1032 mnfree (comp
->c_mn
);
1033 if ((sp
= comp
->c_text
) && (sp
= getname(sp
)) &&
1034 (mn
= getm (sp
, NULL
, 0, AD_NAME
, NULL
))) {
1037 comp
->c_flags
|= CF_TRUE
;
1039 comp
->c_flags
&= ~CF_TRUE
;
1040 while ((sp
= getname(sp
)))
1041 if ((comp
->c_flags
& CF_TRUE
) == 0 &&
1042 (mn
= getm (sp
, NULL
, 0, AD_NAME
, NULL
)))
1044 comp
->c_flags
|= CF_TRUE
;
1046 while (getname("")) /* XXX */
1048 if (comp
->c_text
== 0)
1049 comp
->c_flags
|= CF_TRUE
;
1051 comp
->c_flags
&= ~CF_TRUE
;
1052 comp
->c_mn
= &fmt_mnull
;
1058 * Call our tracing callback function, if one was supplied
1061 if (callbacks
&& callbacks
->trace_func
)
1062 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
1067 /* Emit any trailing sequences of zero display length. */
1068 while (fmt
->f_type
!= FT_DONE
) {
1069 if (fmt
->f_type
== FT_LS_LIT
) {
1071 if (callbacks
&& callbacks
->trace_func
)
1072 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
1074 } else if (fmt
->f_type
== FT_STRLITZ
) {
1075 /* Don't want to emit part of an escape sequence. So if
1076 there isn't enough room in the buffer for the entire
1077 string, skip it completely. Need room for null
1078 terminator, and maybe trailing newline (added below). */
1079 if (cp
- scanl
+ strlen (str
) + 1 < max
) {
1080 for (sp
= str
; *sp
; *cp
++ = *sp
++) continue;
1082 if (callbacks
&& callbacks
->trace_func
)
1083 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
1090 if (cp
> scanl
&& cp
[-1] != '\n') {
1091 if (cp
- scanl
< (int) max
- 1) {
1098 return ((struct format
*)0);