]>
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 struct mailname fmt_mnull
= { NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, 0, 0, 0, 0,
34 static int match (char *, char *);
35 static char *get_x400_friendly (char *, char *, int);
36 static int get_x400_comp (char *, char *, char *, int);
40 * test if string "sub" appears anywhere in
41 * string "str" (case insensitive).
45 match (char *str
, char *sub
)
51 c1
= (isascii((unsigned char) c1
) && isalpha((unsigned char) c1
) &&
52 isupper((unsigned char) c1
)) ? tolower((unsigned char) c1
) : c1
;
53 while ((c2
= *str
++) && c1
!= ((isascii((unsigned char) c2
) &&
54 isalpha((unsigned char) c2
) &&
55 isupper((unsigned char) c2
)) ?
56 tolower((unsigned char) c2
) : c2
))
60 s1
= sub
+ 1; s2
= str
;
61 while ((c1
= *s1
++) && ((isascii((unsigned char) c1
) &&
62 isalpha((unsigned char) c1
) &&
63 isupper((unsigned char) c1
)) ?
65 ((isascii((unsigned char) (c2
=*s2
++)) &&
66 isalpha((unsigned char) c2
) &&
67 isupper((unsigned char) c2
)) ?
68 tolower((unsigned char) c2
) : c2
))
77 * copy a number to the destination subject to a maximum width
80 cpnumber(char **dest
, int num
, unsigned int wid
, char fill
, size_t n
) {
93 *--sp
= (i
% 10) + '0';
95 } while (i
> 0 && sp
> cp
);
98 else if ((num
) < 0 && sp
> cp
)
108 * copy string from str to dest padding with the fill character to a size
109 * of wid characters. if wid is negative, the string is right aligned
110 * no more than n bytes are copied
113 cptrimmed(char **dest
, char **ep
, char *str
, unsigned int wid
, char fill
,
115 int remaining
; /* remaining output width available */
117 int end
; /* number of input bytes remaining in str */
118 #ifdef MULTIBYTE_SUPPORT
119 int char_len
; /* bytes in current character */
124 char *sp
; /* current position in source string */
125 char *cp
= *dest
; /* current position in destination string */
130 if ((remaining
= (wid
)) < 0) {
131 remaining
= -remaining
;
135 #ifdef MULTIBYTE_SUPPORT
136 mbtowc(NULL
, NULL
, 0); /* reset shift state */
139 while (*sp
&& remaining
> 0 && end
> 0) {
140 #ifdef MULTIBYTE_SUPPORT
141 char_len
= mbtowc(&wide_char
, sp
, end
);
144 * See the relevant comments in cpstripped() to explain what's
145 * going on here; we want to handle the case where we get
146 * characters that mbtowc() cannot handle
151 char_len
= mbtowc(&wide_char
, altstr
, 1);
157 w
= wcwidth(wide_char
);
160 * Multibyte characters can have a variable number of column
161 * widths, so use the column width to bump the end pointer when
164 if (w
>= 0 && char_len
> 1 && epmax
- *ep
>= char_len
- w
) {
168 if (w
>= 0 && cp
+ w
> *ep
)
173 if (iswcntrl(wide_char
) || iswspace(wide_char
)) {
178 /* isnctrl(), etc., take an int argument. Cygwin's ctype.h
179 intentionally warns if they are passed a char. */
180 c
= (unsigned char) *sp
;
181 if (iscntrl(c
) || isspace(c
)) {
194 #ifdef MULTIBYTE_SUPPORT
195 if (w
>= 0 && remaining
>= w
) {
196 strncpy(cp
, altstr
? altstr
: sp
, char_len
);
211 if (cp
+ remaining
> *ep
)
212 remaining
= *ep
- cp
;
213 endfield
= cp
+ remaining
;
215 /* copy string to the right */
216 while (--cp
>= *dest
)
217 *(cp
+ remaining
) = *cp
;
218 /* add padding at the beginning */
220 for (c
=remaining
; c
>0; c
--)
225 /* pad remaining space */
226 while (remaining
-- > 0 && cp
< *ep
)
233 cpstripped (char **dest
, char **end
, char *max
, char *str
)
235 int prevCtrl
= 1; /* This is 1 so we strip out leading spaces */
237 #ifdef MULTIBYTE_SUPPORT
241 #endif /* MULTIBYTE_SUPPORT */
248 #ifdef MULTIBYTE_SUPPORT
249 mbtowc(NULL
, NULL
, 0); /* Reset shift state */
250 #endif /* MULTIBYTE_SUPPORT */
253 * Process each character at a time; if we have multibyte support
254 * then deal with that here.
257 while (*str
!= '\0' && len
> 0 && *dest
< *end
) {
258 #ifdef MULTIBYTE_SUPPORT
259 char_len
= mbtowc(&wide_char
, str
, len
);
260 w
= wcwidth(wide_char
);
263 * Account for multibyte characters, and increment the end pointer
264 * by the number of "extra" bytes in this character. That's the
265 * character length (char_len) minus the column width (w).
267 if (w
>= 0 && char_len
> 1 && max
- *end
>= char_len
- w
) {
268 *end
+= char_len
- w
;
272 * If mbrtowc() failed, then we have a character that isn't valid
273 * in the current encoding. Replace it with a '?'. We do that by
274 * setting the alstr variable to the value of the replacement string;
275 * altstr is used below when the bytes are copied into the output
281 char_len
= mbtowc(&wide_char
, altstr
, 1);
284 if (char_len
<= 0 || *dest
+ char_len
> *end
)
289 if (iswcntrl(wide_char
) || iswspace(wide_char
)) {
291 #else /* MULTIBYTE_SUPPORT */
292 int c
= (unsigned char) *str
;
294 if (iscntrl(c
) || isspace(c
)) {
296 #endif /* MULTIBYTE_SUPPORT */
307 #ifdef MULTIBYTE_SUPPORT
308 memcpy(*dest
, altstr
? altstr
: str
, char_len
);
312 #else /* MULTIBYE_SUPPORT */
314 #endif /* MULTIBYTE_SUPPORT */
318 static char *lmonth
[] = { "January", "February","March", "April",
319 "May", "June", "July", "August",
320 "September","October", "November","December" };
323 get_x400_friendly (char *mbox
, char *buffer
, int buffer_len
)
325 char given
[BUFSIZ
], surname
[BUFSIZ
];
334 if (get_x400_comp (mbox
, "/PN=", buffer
, buffer_len
)) {
335 for (mbox
= buffer
; (mbox
= strchr(mbox
, '.')); )
341 if (!get_x400_comp (mbox
, "/S=", surname
, sizeof(surname
)))
344 if (get_x400_comp (mbox
, "/G=", given
, sizeof(given
)))
345 snprintf (buffer
, buffer_len
, "%s %s", given
, surname
);
347 snprintf (buffer
, buffer_len
, "%s", surname
);
353 get_x400_comp (char *mbox
, char *key
, char *buffer
, int buffer_len
)
358 if ((idx
= stringdex (key
, mbox
)) < 0
359 || !(cp
= strchr(mbox
+= idx
+ strlen (key
), '/')))
362 snprintf (buffer
, buffer_len
, "%*.*s", (int)(cp
- mbox
), (int)(cp
- mbox
), mbox
);
367 fmt_scan (struct format
*format
, char *scanl
, size_t max
, int width
, int *dat
,
368 struct fmt_callbacks
*callbacks
)
371 char *savestr
= NULL
, *str
= NULL
;
372 char buffer
[BUFSIZ
], buffer2
[BUFSIZ
];
381 /* ep keeps track of displayed characters. They're limited by width.
382 The total number of characters, cp - scanl + 1 (for trailing NULL),
383 includes invisible control characters and is limited by max. */
385 ep
= scanl
+ (width
<= (int) max
? width
: (int) max
) - 1;
387 for (fmt
= format
; fmt
->f_type
!= FT_DONE
; fmt
++)
388 switch (fmt
->f_type
) {
391 fmt
->f_comp
->c_flags
&= ~CF_PARSED
;
396 case FT_LS_DECODECOMP
:
398 * Trim these components of any newlines.
400 * But don't trim the "body" and "text" components.
405 if (! (comp
->c_flags
& CF_TRIMMED
) && comp
->c_text
&&
406 (i
= strlen(comp
->c_text
)) > 0) {
407 if (comp
->c_text
[i
- 1] == '\n' &&
408 strcmp(comp
->c_name
, "body") != 0 &&
409 strcmp(comp
->c_name
, "text") != 0)
410 comp
->c_text
[i
- 1] = '\0';
411 comp
->c_flags
|= CF_TRIMMED
;
419 switch (fmt
->f_type
) {
422 cpstripped (&cp
, &ep
, scanl
+ max
- 1, fmt
->f_comp
->c_text
);
425 cptrimmed (&cp
, &ep
, fmt
->f_comp
->c_text
, fmt
->f_width
, fmt
->f_fill
,
431 while( (c
= *sp
++) && cp
< ep
)
440 ljust
++; /* XXX should do something with this */
442 while( (c
= *sp
++) && --i
>= 0 && cp
< ep
)
444 while( --i
>= 0 && cp
< ep
)
449 cpstripped (&cp
, &ep
, scanl
+ max
- 1, str
);
452 cptrimmed (&cp
, &ep
, str
, fmt
->f_width
, fmt
->f_fill
,
458 while ((c
= *sp
++) && cp
< ep
)
464 size_t len
= strlen (str
);
466 /* Don't want to emit part of an escape sequence. So if
467 there isn't enough room in the buffer for the entire
468 string, skip it completely. */
469 if (cp
- scanl
+ len
+ 1 < max
) {
470 for (sp
= str
; *sp
; *cp
++ = *sp
++) continue;
472 /* This string doesn't count against the width.
473 So increase ep the same amount as cp, only if the
474 scan buffer will always be large enough. */
475 if (ep
- scanl
+ len
+ 1 < max
) {
482 adios (NULL
, "internal error (FT_STRFW)");
485 n
= snprintf(cp
, ep
- cp
+ 1, "%d", value
);
494 cpnumber (&cp
, value
, fmt
->f_width
, fmt
->f_fill
, ep
- cp
);
502 if (callbacks
&& callbacks
->trace_func
)
503 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
508 if (!(value
= (str
&& *str
))) {
509 if (callbacks
&& callbacks
->trace_func
)
510 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
518 if (!(value
= (str
== NULL
|| *str
== 0))) {
519 if (callbacks
&& callbacks
->trace_func
)
520 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
528 if (value
!= fmt
->f_value
) {
529 if (callbacks
&& callbacks
->trace_func
)
530 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
538 if (value
== fmt
->f_value
) {
539 if (callbacks
&& callbacks
->trace_func
)
540 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
548 if (value
<= fmt
->f_value
) {
549 if (callbacks
&& callbacks
->trace_func
)
550 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
558 if (!(value
= (str
&& match (str
, fmt
->f_text
)))) {
559 if (callbacks
&& callbacks
->trace_func
)
560 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
569 value
= match (str
, fmt
->f_text
);
575 if (!(value
= (str
&& uprf (str
, fmt
->f_text
)))) {
576 if (callbacks
&& callbacks
->trace_func
)
577 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
585 value
= uprf (str
, fmt
->f_text
);
589 value
= (str
!= NULL
&& *str
!= 0);
593 value
= (str
== NULL
|| *str
== 0);
597 value
= (fmt
->f_value
== value
);
601 value
= (fmt
->f_value
!= value
);
605 value
= (fmt
->f_value
> value
);
609 if (callbacks
&& callbacks
->trace_func
)
610 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
619 str
= fmt
->f_comp
->c_text
;
625 if (!(str
= getenv (fmt
->f_text
)))
629 if (!(str
= context_find (fmt
->f_text
)))
633 case FT_LS_DECODECOMP
:
634 if (decode_rfc2047(fmt
->f_comp
->c_text
, buffer2
, sizeof(buffer2
)))
637 str
= fmt
->f_comp
->c_text
;
641 if (str
&& decode_rfc2047(str
, buffer2
, sizeof(buffer2
)))
649 strncpy(buffer
, str
, sizeof(buffer
));
650 buffer
[sizeof(buffer
)-1] = '\0';
652 while (isspace((unsigned char) *str
))
655 if ((i
= fmt
->f_width
) < 0) {
660 if (!ljust
&& i
> 0 && (int) strlen(str
) > i
)
663 xp
+= strlen(str
) - 1;
664 while (xp
> str
&& isspace((unsigned char) *xp
))
666 if (ljust
&& i
> 0 && (int) strlen(str
) > i
)
667 str
+= strlen(str
) - i
;
672 value
= (fmt
->f_comp
->c_flags
& CF_TRUE
) != 0;
675 value
= (comp
= fmt
->f_comp
)->c_text
? atoi(comp
->c_text
) : 0;
678 value
= fmt
->f_value
;
681 value
= dat
[fmt
->f_value
];
689 case FT_LV_CHAR_LEFT
:
690 value
= width
- (cp
- scanl
);
693 value
+= fmt
->f_value
;
696 value
= fmt
->f_value
- value
;
700 value
= value
/ fmt
->f_value
;
706 value
= value
% fmt
->f_value
;
715 value
= fmt
->f_comp
->c_tws
->tw_sec
;
718 value
= fmt
->f_comp
->c_tws
->tw_min
;
721 value
= fmt
->f_comp
->c_tws
->tw_hour
;
724 value
= fmt
->f_comp
->c_tws
->tw_mday
;
727 value
= fmt
->f_comp
->c_tws
->tw_mon
+ 1;
730 str
= tw_moty
[fmt
->f_comp
->c_tws
->tw_mon
];
733 str
= lmonth
[fmt
->f_comp
->c_tws
->tw_mon
];
736 str
= dtwszone (fmt
->f_comp
->c_tws
);
739 value
= fmt
->f_comp
->c_tws
->tw_year
;
742 if (!(((tws
= fmt
->f_comp
->c_tws
)->tw_flags
) & (TW_SEXP
|TW_SIMP
)))
744 value
= tws
->tw_wday
;
747 if (!(((tws
= fmt
->f_comp
->c_tws
)->tw_flags
) & (TW_SEXP
|TW_SIMP
)))
749 str
= tw_dotw
[tws
->tw_wday
];
752 if (!(((tws
= fmt
->f_comp
->c_tws
)->tw_flags
) & (TW_SEXP
|TW_SIMP
)))
754 str
= tw_ldotw
[tws
->tw_wday
];
757 value
= fmt
->f_comp
->c_tws
->tw_yday
;
760 value
= fmt
->f_comp
->c_tws
->tw_zone
;
763 if ((value
= fmt
->f_comp
->c_tws
->tw_clock
) == 0)
764 value
= dmktime(fmt
->f_comp
->c_tws
);
767 if ((value
= fmt
->f_comp
->c_tws
->tw_clock
) == 0)
768 value
= dmktime(fmt
->f_comp
->c_tws
);
769 value
= time((time_t *) 0) - value
;
772 if (!(((tws
= fmt
->f_comp
->c_tws
)->tw_flags
) & (TW_SEXP
|TW_SIMP
)))
774 switch (fmt
->f_comp
->c_tws
->tw_flags
& TW_SDAY
) {
783 if ((fmt
->f_comp
->c_tws
->tw_flags
& TW_SZONE
) == TW_SZEXP
)
789 value
= fmt
->f_comp
->c_tws
->tw_flags
& TW_DST
;
792 str
= dasctime (fmt
->f_comp
->c_tws
, TW_ZONE
);
795 str
= dasctime (fmt
->f_comp
->c_tws
, TW_NULL
);
799 str
= fmt
->f_comp
->c_mn
->m_pers
;
802 str
= fmt
->f_comp
->c_mn
->m_mbox
;
805 str
= fmt
->f_comp
->c_mn
->m_host
;
808 str
= fmt
->f_comp
->c_mn
->m_path
;
811 str
= fmt
->f_comp
->c_mn
->m_gname
;
814 str
= fmt
->f_comp
->c_mn
->m_note
;
817 str
= adrformat( fmt
->f_comp
->c_mn
);
820 value
= fmt
->f_comp
->c_mn
->m_type
;
823 value
= fmt
->f_comp
->c_mn
->m_ingrp
;
826 value
= fmt
->f_comp
->c_mn
->m_nohost
;
830 if ((mn
= fmt
->f_comp
->c_mn
) == &fmt_mnull
) {
831 str
= fmt
->f_comp
->c_text
;
834 if (fmt
->f_type
== FT_LS_ADDR
)
836 if ((str
= mn
->m_pers
) == NULL
) {
837 if ((str
= mn
->m_note
)) {
838 strncpy (buffer
, str
, sizeof(buffer
));
839 buffer
[sizeof(buffer
)-1] = '\0';
843 sp
= str
+ strlen(str
) - 1;
852 } else if (!(str
= get_x400_friendly (mn
->m_mbox
,
853 buffer
, sizeof(buffer
)))) {
855 switch (mn
->m_type
) {
860 snprintf (buffer
, sizeof(buffer
), "%s!%s",
861 mn
->m_host
, mn
->m_mbox
);
866 snprintf (buffer
, sizeof(buffer
), "%s@%s",
867 mn
->m_mbox
, mn
->m_host
);
879 /* UNQUOTEs RFC-2822 quoted-string and quoted-pair */
882 strncpy(buffer
, str
, sizeof(buffer
));
883 /* strncpy doesn't NUL-terminate if it fills the buffer */
884 buffer
[sizeof(buffer
)-1] = '\0';
885 unquote_string(buffer
, buffer2
);
892 if ((t
= comp
->c_tws
->tw_clock
) == 0)
893 t
= dmktime(comp
->c_tws
);
894 tws
= dlocaltime(&t
);
900 if ((t
= comp
->c_tws
->tw_clock
) == 0)
901 t
= dmktime(comp
->c_tws
);
908 if (comp
->c_flags
& CF_PARSED
)
910 if ((sp
= comp
->c_text
) && (tws
= dparsetime(sp
))) {
912 comp
->c_flags
&= ~CF_TRUE
;
913 } else if ((comp
->c_flags
& CF_DATEFAB
) == 0) {
914 memset ((char *) comp
->c_tws
, 0, sizeof *comp
->c_tws
);
915 comp
->c_flags
= CF_TRUE
;
917 comp
->c_flags
|= CF_PARSED
;
921 /* hook for custom address list formatting (see replsbr.c) */
922 if (callbacks
&& callbacks
->formataddr
)
923 str
= callbacks
->formataddr (savestr
, str
);
925 str
= formataddr (savestr
, str
);
929 /* The same as formataddr, but doesn't do duplicate suppression */
930 if (callbacks
&& callbacks
->concataddr
)
931 str
= callbacks
->concataddr (savestr
, str
);
933 str
= concataddr (savestr
, str
);
937 /* output the str register as an address component,
938 * splitting it into multiple lines if necessary. The
939 * value reg. contains the max line length. The lit.
940 * field may contain a string to prepend to the result
945 int indent
, wid
, len
;
951 indent
= strlen (sp
);
954 adios(NULL
, "putaddr -- num register (%d) must be greater "
955 "than label width (%d)", value
, indent
);
957 while( (c
= (unsigned char) *sp
++) && cp
< ep
)
960 /* try to break at a comma; failing that, break at a
963 lastb
= 0; sp
= lp
+ wid
;
964 while (sp
> lp
&& (c
= (unsigned char) *--sp
) != ',') {
965 if (! lastb
&& isspace(c
))
969 if (! (sp
= lastb
)) {
971 while (*sp
&& *sp
!= ',' &&
972 !isspace((unsigned char) *sp
))
979 while (cp
< ep
&& lp
<= sp
)
981 while (isspace((unsigned char) *lp
))
986 for (i
=indent
; cp
< ep
&& i
> 0; i
--)
990 cpstripped (&cp
, &ep
, scanl
+ max
- 1, lp
);
996 if (comp
->c_flags
& CF_PARSED
)
998 if (comp
->c_mn
!= &fmt_mnull
)
1000 if ((sp
= comp
->c_text
) && (sp
= getname(sp
)) &&
1001 (mn
= getm (sp
, NULL
, 0, NULL
, 0))) {
1005 comp
->c_flags
|= CF_PARSED
;
1007 while (getname("")) /* XXX */
1009 comp
->c_mn
= &fmt_mnull
;
1015 * if there's no component, we say true. Otherwise we
1016 * say "true" only if we can parse the address and it
1017 * matches one of our addresses.
1020 if (comp
->c_mn
!= &fmt_mnull
)
1021 mnfree (comp
->c_mn
);
1022 if ((sp
= comp
->c_text
) && (sp
= getname(sp
)) &&
1023 (mn
= getm (sp
, NULL
, 0, NULL
, 0))) {
1026 comp
->c_flags
|= CF_TRUE
;
1028 comp
->c_flags
&= ~CF_TRUE
;
1029 while ((sp
= getname(sp
)))
1030 if ((comp
->c_flags
& CF_TRUE
) == 0 &&
1031 (mn
= getm (sp
, NULL
, 0, NULL
, 0)))
1033 comp
->c_flags
|= CF_TRUE
;
1035 while (getname("")) /* XXX */
1037 if (comp
->c_text
== 0)
1038 comp
->c_flags
|= CF_TRUE
;
1040 comp
->c_flags
&= ~CF_TRUE
;
1041 comp
->c_mn
= &fmt_mnull
;
1047 * Call our tracing callback function, if one was supplied
1050 if (callbacks
&& callbacks
->trace_func
)
1051 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
1056 /* Emit any trailing sequences of zero display length. */
1057 while (fmt
->f_type
!= FT_DONE
) {
1058 if (fmt
->f_type
== FT_LS_LIT
) {
1060 if (callbacks
&& callbacks
->trace_func
)
1061 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
1063 } else if (fmt
->f_type
== FT_STRLITZ
) {
1064 /* Don't want to emit part of an escape sequence. So if
1065 there isn't enough room in the buffer for the entire
1066 string, skip it completely. Need room for null
1067 terminator, and maybe trailing newline (added below). */
1068 if (str
&& (cp
- scanl
+ strlen (str
) + 1 < max
)) {
1069 for (sp
= str
; *sp
; *cp
++ = *sp
++) continue;
1071 if (callbacks
&& callbacks
->trace_func
)
1072 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
1079 if (cp
> scanl
&& cp
[-1] != '\n') {
1080 if (cp
- scanl
< (int) max
- 1) {
1087 return ((struct format
*)0);