]>
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
);
496 unsigned int whole
, tenths
;
499 snprintf(buffer
, sizeof(buffer
), "%u", value
);
502 /* find correct scale for size (Kilo/Mega/Giga/Tera) */
503 for (unitcp
= "KMGT"; value
> 999999; value
/= 1000) {
509 strcpy(buffer
, "huge");
511 /* round up to next higer tenth of kilo-/mega-/giga- */
512 value
+= 99; value
/= 100;
514 /* want one digit after decimal, avoid float */
516 tenths
= value
- (whole
* 10);
519 snprintf(buffer
, sizeof(buffer
), "%u.%u%c", whole
, tenths
, *unitcp
);
521 snprintf(buffer
, sizeof(buffer
), "%u%c", whole
, *unitcp
);
529 cpnumber (&cp
, value
, fmt
->f_width
, fmt
->f_fill
, ep
- cp
);
537 if (callbacks
&& callbacks
->trace_func
)
538 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
543 if (!(value
= (str
&& *str
))) {
544 if (callbacks
&& callbacks
->trace_func
)
545 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
553 if (!(value
= (str
== NULL
|| *str
== 0))) {
554 if (callbacks
&& callbacks
->trace_func
)
555 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
563 if (value
!= fmt
->f_value
) {
564 if (callbacks
&& callbacks
->trace_func
)
565 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
573 if (value
== fmt
->f_value
) {
574 if (callbacks
&& callbacks
->trace_func
)
575 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
583 if (value
<= fmt
->f_value
) {
584 if (callbacks
&& callbacks
->trace_func
)
585 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
593 if (!(value
= (str
&& match (str
, fmt
->f_text
)))) {
594 if (callbacks
&& callbacks
->trace_func
)
595 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
604 value
= match (str
, fmt
->f_text
);
610 if (!(value
= (str
&& uprf (str
, fmt
->f_text
)))) {
611 if (callbacks
&& callbacks
->trace_func
)
612 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
620 value
= uprf (str
, fmt
->f_text
);
624 value
= (str
!= NULL
&& *str
!= 0);
628 value
= (str
== NULL
|| *str
== 0);
632 value
= (fmt
->f_value
== value
);
636 value
= (fmt
->f_value
!= value
);
640 value
= (fmt
->f_value
> value
);
644 if (callbacks
&& callbacks
->trace_func
)
645 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
654 str
= fmt
->f_comp
->c_text
;
660 if (!(str
= getenv (fmt
->f_text
)))
664 if (!(str
= context_find (fmt
->f_text
)))
668 case FT_LS_DECODECOMP
:
669 if (decode_rfc2047(fmt
->f_comp
->c_text
, buffer2
, sizeof(buffer2
)))
672 str
= fmt
->f_comp
->c_text
;
676 if (str
&& decode_rfc2047(str
, buffer2
, sizeof(buffer2
)))
684 strncpy(buffer
, str
, sizeof(buffer
));
685 buffer
[sizeof(buffer
)-1] = '\0';
687 while (isspace((unsigned char) *str
))
690 if ((i
= fmt
->f_width
) < 0) {
695 if (!ljust
&& i
> 0 && (int) strlen(str
) > i
)
698 xp
+= strlen(str
) - 1;
699 while (xp
> str
&& isspace((unsigned char) *xp
))
701 if (ljust
&& i
> 0 && (int) strlen(str
) > i
)
702 str
+= strlen(str
) - i
;
707 value
= (fmt
->f_comp
->c_flags
& CF_TRUE
) != 0;
710 value
= (comp
= fmt
->f_comp
)->c_text
? atoi(comp
->c_text
) : 0;
713 value
= fmt
->f_value
;
716 value
= dat
[fmt
->f_value
];
724 case FT_LV_CHAR_LEFT
:
725 value
= width
- (cp
- scanl
);
728 value
+= fmt
->f_value
;
731 value
= fmt
->f_value
- value
;
735 value
= value
/ fmt
->f_value
;
741 value
= value
% fmt
->f_value
;
750 value
= fmt
->f_comp
->c_tws
->tw_sec
;
753 value
= fmt
->f_comp
->c_tws
->tw_min
;
756 value
= fmt
->f_comp
->c_tws
->tw_hour
;
759 value
= fmt
->f_comp
->c_tws
->tw_mday
;
762 value
= fmt
->f_comp
->c_tws
->tw_mon
+ 1;
765 str
= tw_moty
[fmt
->f_comp
->c_tws
->tw_mon
];
768 str
= lmonth
[fmt
->f_comp
->c_tws
->tw_mon
];
771 str
= dtwszone (fmt
->f_comp
->c_tws
);
774 value
= fmt
->f_comp
->c_tws
->tw_year
;
777 if (!(((tws
= fmt
->f_comp
->c_tws
)->tw_flags
) & (TW_SEXP
|TW_SIMP
)))
779 value
= tws
->tw_wday
;
782 if (!(((tws
= fmt
->f_comp
->c_tws
)->tw_flags
) & (TW_SEXP
|TW_SIMP
)))
784 str
= tw_dotw
[tws
->tw_wday
];
787 if (!(((tws
= fmt
->f_comp
->c_tws
)->tw_flags
) & (TW_SEXP
|TW_SIMP
)))
789 str
= tw_ldotw
[tws
->tw_wday
];
792 value
= fmt
->f_comp
->c_tws
->tw_yday
;
795 value
= fmt
->f_comp
->c_tws
->tw_zone
;
798 if ((value
= fmt
->f_comp
->c_tws
->tw_clock
) == 0)
799 value
= dmktime(fmt
->f_comp
->c_tws
);
802 if ((value
= fmt
->f_comp
->c_tws
->tw_clock
) == 0)
803 value
= dmktime(fmt
->f_comp
->c_tws
);
804 value
= time((time_t *) 0) - value
;
807 if (!(((tws
= fmt
->f_comp
->c_tws
)->tw_flags
) & (TW_SEXP
|TW_SIMP
)))
809 switch (fmt
->f_comp
->c_tws
->tw_flags
& TW_SDAY
) {
818 if ((fmt
->f_comp
->c_tws
->tw_flags
& TW_SZONE
) == TW_SZEXP
)
824 value
= fmt
->f_comp
->c_tws
->tw_flags
& TW_DST
;
827 str
= dasctime (fmt
->f_comp
->c_tws
, TW_ZONE
);
830 str
= dasctime (fmt
->f_comp
->c_tws
, TW_NULL
);
834 str
= fmt
->f_comp
->c_mn
->m_pers
;
837 str
= fmt
->f_comp
->c_mn
->m_mbox
;
840 str
= fmt
->f_comp
->c_mn
->m_host
;
843 str
= fmt
->f_comp
->c_mn
->m_path
;
846 str
= fmt
->f_comp
->c_mn
->m_gname
;
849 str
= fmt
->f_comp
->c_mn
->m_note
;
852 str
= adrformat( fmt
->f_comp
->c_mn
);
855 value
= fmt
->f_comp
->c_mn
->m_type
;
858 value
= fmt
->f_comp
->c_mn
->m_ingrp
;
861 value
= fmt
->f_comp
->c_mn
->m_nohost
;
865 if ((mn
= fmt
->f_comp
->c_mn
) == &fmt_mnull
) {
866 str
= fmt
->f_comp
->c_text
;
869 if (fmt
->f_type
== FT_LS_ADDR
)
871 if ((str
= mn
->m_pers
) == NULL
) {
872 if ((str
= mn
->m_note
)) {
873 strncpy (buffer
, str
, sizeof(buffer
));
874 buffer
[sizeof(buffer
)-1] = '\0';
878 sp
= str
+ strlen(str
) - 1;
887 } else if (!(str
= get_x400_friendly (mn
->m_mbox
,
888 buffer
, sizeof(buffer
)))) {
890 switch (mn
->m_type
) {
895 snprintf (buffer
, sizeof(buffer
), "%s!%s",
896 mn
->m_host
, mn
->m_mbox
);
901 snprintf (buffer
, sizeof(buffer
), "%s@%s",
902 mn
->m_mbox
, mn
->m_host
);
914 /* UNQUOTEs RFC-2822 quoted-string and quoted-pair */
917 strncpy(buffer
, str
, sizeof(buffer
));
918 /* strncpy doesn't NUL-terminate if it fills the buffer */
919 buffer
[sizeof(buffer
)-1] = '\0';
920 unquote_string(buffer
, buffer2
);
927 if ((t
= comp
->c_tws
->tw_clock
) == 0)
928 t
= dmktime(comp
->c_tws
);
929 tws
= dlocaltime(&t
);
935 if ((t
= comp
->c_tws
->tw_clock
) == 0)
936 t
= dmktime(comp
->c_tws
);
943 if (comp
->c_flags
& CF_PARSED
)
945 if ((sp
= comp
->c_text
) && (tws
= dparsetime(sp
))) {
947 comp
->c_flags
&= ~CF_TRUE
;
948 } else if ((comp
->c_flags
& CF_DATEFAB
) == 0) {
949 memset ((char *) comp
->c_tws
, 0, sizeof *comp
->c_tws
);
950 comp
->c_flags
= CF_TRUE
;
952 comp
->c_flags
|= CF_PARSED
;
956 /* hook for custom address list formatting (see replsbr.c) */
957 if (callbacks
&& callbacks
->formataddr
)
958 str
= callbacks
->formataddr (savestr
, str
);
960 str
= formataddr (savestr
, str
);
964 /* The same as formataddr, but doesn't do duplicate suppression */
965 if (callbacks
&& callbacks
->concataddr
)
966 str
= callbacks
->concataddr (savestr
, str
);
968 str
= concataddr (savestr
, str
);
972 /* output the str register as an address component,
973 * splitting it into multiple lines if necessary. The
974 * value reg. contains the max line length. The lit.
975 * field may contain a string to prepend to the result
980 int indent
, wid
, len
;
986 indent
= strlen (sp
);
989 adios(NULL
, "putaddr -- num register (%d) must be greater "
990 "than label width (%d)", value
, indent
);
992 while( (c
= (unsigned char) *sp
++) && cp
< ep
)
995 /* try to break at a comma; failing that, break at a
998 lastb
= 0; sp
= lp
+ wid
;
999 while (sp
> lp
&& (c
= (unsigned char) *--sp
) != ',') {
1000 if (! lastb
&& isspace(c
))
1004 if (! (sp
= lastb
)) {
1006 while (*sp
&& *sp
!= ',' &&
1007 !isspace((unsigned char) *sp
))
1014 while (cp
< ep
&& lp
<= sp
)
1016 while (isspace((unsigned char) *lp
))
1021 for (i
=indent
; cp
< ep
&& i
> 0; i
--)
1025 cpstripped (&cp
, &ep
, scanl
+ max
- 1, lp
);
1031 if (comp
->c_flags
& CF_PARSED
)
1033 if (comp
->c_mn
!= &fmt_mnull
)
1034 mnfree (comp
->c_mn
);
1035 if ((sp
= comp
->c_text
) && (sp
= getname(sp
)) &&
1036 (mn
= getm (sp
, NULL
, 0, NULL
, 0))) {
1040 comp
->c_flags
|= CF_PARSED
;
1042 while (getname("")) /* XXX */
1044 comp
->c_mn
= &fmt_mnull
;
1050 * if there's no component, we say true. Otherwise we
1051 * say "true" only if we can parse the address and it
1052 * matches one of our addresses.
1055 if (comp
->c_mn
!= &fmt_mnull
)
1056 mnfree (comp
->c_mn
);
1057 if ((sp
= comp
->c_text
) && (sp
= getname(sp
)) &&
1058 (mn
= getm (sp
, NULL
, 0, NULL
, 0))) {
1061 comp
->c_flags
|= CF_TRUE
;
1063 comp
->c_flags
&= ~CF_TRUE
;
1064 while ((sp
= getname(sp
)))
1065 if ((comp
->c_flags
& CF_TRUE
) == 0 &&
1066 (mn
= getm (sp
, NULL
, 0, NULL
, 0)))
1068 comp
->c_flags
|= CF_TRUE
;
1070 while (getname("")) /* XXX */
1072 if (comp
->c_text
== 0)
1073 comp
->c_flags
|= CF_TRUE
;
1075 comp
->c_flags
&= ~CF_TRUE
;
1076 comp
->c_mn
= &fmt_mnull
;
1082 * Call our tracing callback function, if one was supplied
1085 if (callbacks
&& callbacks
->trace_func
)
1086 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
1091 /* Emit any trailing sequences of zero display length. */
1092 while (fmt
->f_type
!= FT_DONE
) {
1093 if (fmt
->f_type
== FT_LS_LIT
) {
1095 if (callbacks
&& callbacks
->trace_func
)
1096 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
1098 } else if (fmt
->f_type
== FT_STRLITZ
) {
1099 /* Don't want to emit part of an escape sequence. So if
1100 there isn't enough room in the buffer for the entire
1101 string, skip it completely. Need room for null
1102 terminator, and maybe trailing newline (added below). */
1103 if (str
&& (cp
- scanl
+ strlen (str
) + 1 < max
)) {
1104 for (sp
= str
; *sp
; *cp
++ = *sp
++) continue;
1106 if (callbacks
&& callbacks
->trace_func
)
1107 callbacks
->trace_func(callbacks
->trace_context
, fmt
, value
,
1114 if (cp
> scanl
&& cp
[-1] != '\n') {
1115 if (cp
- scanl
< (int) max
- 1) {
1122 return ((struct format
*)0);