Fossil SCM

Improvements to the way that searchable text is parsed out of documents, wiki, and check-in comments. The first line of the text is the title. Subsequent lines are message body. Still need to do this for tickets.

drh 2015-02-12 02:32 UTC search-enhancements
Commit 837d9b5b18d9852365068105cbc58f2f8acc973a
3 files changed +1 -1 +57 -4 +25 -8
+1 -1
--- src/main.mk
+++ src/main.mk
@@ -491,11 +491,11 @@
491491
$(OBJDIR)/cson_amalgamation.o
492492
493493
494494
$(APPNAME): $(OBJDIR)/headers $(OBJDIR)/codecheck1 $(OBJ) $(EXTRAOBJ)
495495
$(OBJDIR)/codecheck1 $(TRANS_SRC)
496
- $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB)
496
+ $(TCC) $(CFLAGS) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB)
497497
498498
# This rule prevents make from using its default rules to try build
499499
# an executable named "manifest" out of the file named "manifest.c"
500500
#
501501
$(SRCDIR)/../manifest:
502502
--- src/main.mk
+++ src/main.mk
@@ -491,11 +491,11 @@
491 $(OBJDIR)/cson_amalgamation.o
492
493
494 $(APPNAME): $(OBJDIR)/headers $(OBJDIR)/codecheck1 $(OBJ) $(EXTRAOBJ)
495 $(OBJDIR)/codecheck1 $(TRANS_SRC)
496 $(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB)
497
498 # This rule prevents make from using its default rules to try build
499 # an executable named "manifest" out of the file named "manifest.c"
500 #
501 $(SRCDIR)/../manifest:
502
--- src/main.mk
+++ src/main.mk
@@ -491,11 +491,11 @@
491 $(OBJDIR)/cson_amalgamation.o
492
493
494 $(APPNAME): $(OBJDIR)/headers $(OBJDIR)/codecheck1 $(OBJ) $(EXTRAOBJ)
495 $(OBJDIR)/codecheck1 $(TRANS_SRC)
496 $(TCC) $(CFLAGS) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB)
497
498 # This rule prevents make from using its default rules to try build
499 # an executable named "manifest" out of the file named "manifest.c"
500 #
501 $(SRCDIR)/../manifest:
502
+57 -4
--- src/search.c
+++ src/search.c
@@ -983,27 +983,48 @@
983983
984984
985985
/*
986986
** This is a helper function for search_stext(). Writing into pOut
987987
** the search text obtained from pIn according to zMimetype.
988
+**
989
+** The title of the document is the first line of text. All subsequent
990
+** lines are the body. If the document has no title, the first line
991
+** is blank.
988992
*/
989993
static void get_stext_by_mimetype(
990994
Blob *pIn,
991995
const char *zMimetype,
992996
Blob *pOut
993997
){
994
- Blob html, title;
998
+ Blob html, title, tail;
995999
blob_init(&html, 0, 0);
9961000
blob_init(&title, 0, 0);
9971001
if( zMimetype==0 ) zMimetype = "text/plain";
9981002
if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 ){
999
- wiki_convert(pIn, &html, 0);
1003
+ Blob tail;
1004
+ blob_init(&tail, 0, 0);
1005
+ if( wiki_find_title(pIn, &title, &tail) ){
1006
+ blob_appendf(pOut, "%s\n", blob_str(&title));
1007
+ wiki_convert(&tail, &html, 0);
1008
+ blob_reset(&tail);
1009
+ }else{
1010
+ blob_append(pOut, "\n", 1);
1011
+ wiki_convert(pIn, &html, 0);
1012
+ }
10001013
html_to_plaintext(blob_str(&html), pOut);
10011014
}else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){
10021015
markdown_to_html(pIn, &title, &html);
1016
+ if( blob_size(&title) ){
1017
+ blob_appendf(pOut, "%s\n", blob_str(&title));
1018
+ }else{
1019
+ blob_append(pOut, "\n", 1);
1020
+ }
10031021
html_to_plaintext(blob_str(&html), pOut);
10041022
}else if( fossil_strcmp(zMimetype,"text/html")==0 ){
1023
+ if( doc_is_embedded_html(pIn, &title) ){
1024
+ blob_appendf(pOut, "%s\n", blob_str(&title));
1025
+ }
10051026
html_to_plaintext(blob_str(pIn), pOut);
10061027
}else{
10071028
*pOut = *pIn;
10081029
blob_init(pIn, 0, 0);
10091030
}
@@ -1054,11 +1075,11 @@
10541075
){
10551076
blob_init(pOut, 0, 0);
10561077
switch( cType ){
10571078
case 'd': { /* Documents */
10581079
Blob doc;
1059
- content_get(rid, &doc);
1080
+ content_get(rid, &doc);
10601081
blob_to_utf8_no_bom(&doc, 0);
10611082
get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut);
10621083
blob_reset(&doc);
10631084
break;
10641085
}
@@ -1073,10 +1094,11 @@
10731094
manifest_destroy(pWiki);
10741095
break;
10751096
}
10761097
case 'c': { /* Check-in Comments */
10771098
static Stmt q;
1099
+ static int isPlainText = -1;
10781100
db_static_prepare(&q,
10791101
"SELECT coalesce(ecomment,comment)"
10801102
" ||' (user: '||coalesce(euser,user,'?')"
10811103
" ||', tags: '||"
10821104
" (SELECT group_concat(substr(tag.tagname,5),',')"
@@ -1083,14 +1105,25 @@
10831105
" FROM tag, tagxref"
10841106
" WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
10851107
" AND tagxref.rid=event.objid AND tagxref.tagtype>0)"
10861108
" ||')'"
10871109
" FROM event WHERE objid=:x AND type='ci'");
1110
+ if( isPlainText<0 ){
1111
+ isPlainText = db_get_boolean("timeline-plaintext",0);
1112
+ }
10881113
db_bind_int(&q, ":x", rid);
10891114
if( db_step(&q)==SQLITE_ROW ){
1090
- db_column_blob(&q, 0, pOut);
10911115
blob_append(pOut, "\n", 1);
1116
+ if( isPlainText ){
1117
+ db_column_blob(&q, 0, pOut);
1118
+ }else{
1119
+ Blob x;
1120
+ blob_init(&x,0,0);
1121
+ db_column_blob(&q, 0, &x);
1122
+ get_stext_by_mimetype(&x, "text/x-fossil-wiki", pOut);
1123
+ blob_reset(&x);
1124
+ }
10921125
}
10931126
db_reset(&q);
10941127
break;
10951128
}
10961129
case 't': { /* Tickets */
@@ -1131,10 +1164,30 @@
11311164
if( g.argc!=5 ) usage("TYPE RID NAME");
11321165
search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out);
11331166
fossil_print("%s\n",blob_str(&out));
11341167
blob_reset(&out);
11351168
}
1169
+
1170
+/*
1171
+** COMMAND: test-convert-stext
1172
+**
1173
+** Usage: fossil test-convert-stext FILE MIMETYPE
1174
+**
1175
+** Read the content of FILE and convert it to stext according to MIMETYPE.
1176
+** Send the result to standard output.
1177
+*/
1178
+void test_convert_stext(void){
1179
+ Blob in, out;
1180
+ db_find_and_open_repository(0,0);
1181
+ if( g.argc!=4 ) usage("FILENAME MIMETYPE");
1182
+ blob_read_from_file(&in, g.argv[2]);
1183
+ blob_init(&out, 0, 0);
1184
+ get_stext_by_mimetype(&in, g.argv[3], &out);
1185
+ fossil_print("%s\n",blob_str(&out));
1186
+ blob_reset(&in);
1187
+ blob_reset(&out);
1188
+}
11361189
11371190
/* The schema for the full-text index
11381191
*/
11391192
static const char zFtsSchema[] =
11401193
@ -- One entry for each possible search result
11411194
--- src/search.c
+++ src/search.c
@@ -983,27 +983,48 @@
983
984
985 /*
986 ** This is a helper function for search_stext(). Writing into pOut
987 ** the search text obtained from pIn according to zMimetype.
 
 
 
 
988 */
989 static void get_stext_by_mimetype(
990 Blob *pIn,
991 const char *zMimetype,
992 Blob *pOut
993 ){
994 Blob html, title;
995 blob_init(&html, 0, 0);
996 blob_init(&title, 0, 0);
997 if( zMimetype==0 ) zMimetype = "text/plain";
998 if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 ){
999 wiki_convert(pIn, &html, 0);
 
 
 
 
 
 
 
 
 
1000 html_to_plaintext(blob_str(&html), pOut);
1001 }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){
1002 markdown_to_html(pIn, &title, &html);
 
 
 
 
 
1003 html_to_plaintext(blob_str(&html), pOut);
1004 }else if( fossil_strcmp(zMimetype,"text/html")==0 ){
 
 
 
1005 html_to_plaintext(blob_str(pIn), pOut);
1006 }else{
1007 *pOut = *pIn;
1008 blob_init(pIn, 0, 0);
1009 }
@@ -1054,11 +1075,11 @@
1054 ){
1055 blob_init(pOut, 0, 0);
1056 switch( cType ){
1057 case 'd': { /* Documents */
1058 Blob doc;
1059 content_get(rid, &doc);
1060 blob_to_utf8_no_bom(&doc, 0);
1061 get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut);
1062 blob_reset(&doc);
1063 break;
1064 }
@@ -1073,10 +1094,11 @@
1073 manifest_destroy(pWiki);
1074 break;
1075 }
1076 case 'c': { /* Check-in Comments */
1077 static Stmt q;
 
1078 db_static_prepare(&q,
1079 "SELECT coalesce(ecomment,comment)"
1080 " ||' (user: '||coalesce(euser,user,'?')"
1081 " ||', tags: '||"
1082 " (SELECT group_concat(substr(tag.tagname,5),',')"
@@ -1083,14 +1105,25 @@
1083 " FROM tag, tagxref"
1084 " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
1085 " AND tagxref.rid=event.objid AND tagxref.tagtype>0)"
1086 " ||')'"
1087 " FROM event WHERE objid=:x AND type='ci'");
 
 
 
1088 db_bind_int(&q, ":x", rid);
1089 if( db_step(&q)==SQLITE_ROW ){
1090 db_column_blob(&q, 0, pOut);
1091 blob_append(pOut, "\n", 1);
 
 
 
 
 
 
 
 
 
1092 }
1093 db_reset(&q);
1094 break;
1095 }
1096 case 't': { /* Tickets */
@@ -1131,10 +1164,30 @@
1131 if( g.argc!=5 ) usage("TYPE RID NAME");
1132 search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out);
1133 fossil_print("%s\n",blob_str(&out));
1134 blob_reset(&out);
1135 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1136
1137 /* The schema for the full-text index
1138 */
1139 static const char zFtsSchema[] =
1140 @ -- One entry for each possible search result
1141
--- src/search.c
+++ src/search.c
@@ -983,27 +983,48 @@
983
984
985 /*
986 ** This is a helper function for search_stext(). Writing into pOut
987 ** the search text obtained from pIn according to zMimetype.
988 **
989 ** The title of the document is the first line of text. All subsequent
990 ** lines are the body. If the document has no title, the first line
991 ** is blank.
992 */
993 static void get_stext_by_mimetype(
994 Blob *pIn,
995 const char *zMimetype,
996 Blob *pOut
997 ){
998 Blob html, title, tail;
999 blob_init(&html, 0, 0);
1000 blob_init(&title, 0, 0);
1001 if( zMimetype==0 ) zMimetype = "text/plain";
1002 if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 ){
1003 Blob tail;
1004 blob_init(&tail, 0, 0);
1005 if( wiki_find_title(pIn, &title, &tail) ){
1006 blob_appendf(pOut, "%s\n", blob_str(&title));
1007 wiki_convert(&tail, &html, 0);
1008 blob_reset(&tail);
1009 }else{
1010 blob_append(pOut, "\n", 1);
1011 wiki_convert(pIn, &html, 0);
1012 }
1013 html_to_plaintext(blob_str(&html), pOut);
1014 }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){
1015 markdown_to_html(pIn, &title, &html);
1016 if( blob_size(&title) ){
1017 blob_appendf(pOut, "%s\n", blob_str(&title));
1018 }else{
1019 blob_append(pOut, "\n", 1);
1020 }
1021 html_to_plaintext(blob_str(&html), pOut);
1022 }else if( fossil_strcmp(zMimetype,"text/html")==0 ){
1023 if( doc_is_embedded_html(pIn, &title) ){
1024 blob_appendf(pOut, "%s\n", blob_str(&title));
1025 }
1026 html_to_plaintext(blob_str(pIn), pOut);
1027 }else{
1028 *pOut = *pIn;
1029 blob_init(pIn, 0, 0);
1030 }
@@ -1054,11 +1075,11 @@
1075 ){
1076 blob_init(pOut, 0, 0);
1077 switch( cType ){
1078 case 'd': { /* Documents */
1079 Blob doc;
1080 content_get(rid, &doc);
1081 blob_to_utf8_no_bom(&doc, 0);
1082 get_stext_by_mimetype(&doc, mimetype_from_name(zName), pOut);
1083 blob_reset(&doc);
1084 break;
1085 }
@@ -1073,10 +1094,11 @@
1094 manifest_destroy(pWiki);
1095 break;
1096 }
1097 case 'c': { /* Check-in Comments */
1098 static Stmt q;
1099 static int isPlainText = -1;
1100 db_static_prepare(&q,
1101 "SELECT coalesce(ecomment,comment)"
1102 " ||' (user: '||coalesce(euser,user,'?')"
1103 " ||', tags: '||"
1104 " (SELECT group_concat(substr(tag.tagname,5),',')"
@@ -1083,14 +1105,25 @@
1105 " FROM tag, tagxref"
1106 " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
1107 " AND tagxref.rid=event.objid AND tagxref.tagtype>0)"
1108 " ||')'"
1109 " FROM event WHERE objid=:x AND type='ci'");
1110 if( isPlainText<0 ){
1111 isPlainText = db_get_boolean("timeline-plaintext",0);
1112 }
1113 db_bind_int(&q, ":x", rid);
1114 if( db_step(&q)==SQLITE_ROW ){
 
1115 blob_append(pOut, "\n", 1);
1116 if( isPlainText ){
1117 db_column_blob(&q, 0, pOut);
1118 }else{
1119 Blob x;
1120 blob_init(&x,0,0);
1121 db_column_blob(&q, 0, &x);
1122 get_stext_by_mimetype(&x, "text/x-fossil-wiki", pOut);
1123 blob_reset(&x);
1124 }
1125 }
1126 db_reset(&q);
1127 break;
1128 }
1129 case 't': { /* Tickets */
@@ -1131,10 +1164,30 @@
1164 if( g.argc!=5 ) usage("TYPE RID NAME");
1165 search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out);
1166 fossil_print("%s\n",blob_str(&out));
1167 blob_reset(&out);
1168 }
1169
1170 /*
1171 ** COMMAND: test-convert-stext
1172 **
1173 ** Usage: fossil test-convert-stext FILE MIMETYPE
1174 **
1175 ** Read the content of FILE and convert it to stext according to MIMETYPE.
1176 ** Send the result to standard output.
1177 */
1178 void test_convert_stext(void){
1179 Blob in, out;
1180 db_find_and_open_repository(0,0);
1181 if( g.argc!=4 ) usage("FILENAME MIMETYPE");
1182 blob_read_from_file(&in, g.argv[2]);
1183 blob_init(&out, 0, 0);
1184 get_stext_by_mimetype(&in, g.argv[3], &out);
1185 fossil_print("%s\n",blob_str(&out));
1186 blob_reset(&in);
1187 blob_reset(&out);
1188 }
1189
1190 /* The schema for the full-text index
1191 */
1192 static const char zFtsSchema[] =
1193 @ -- One entry for each possible search result
1194
+25 -8
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -2109,14 +2109,19 @@
21092109
}
21102110
21112111
/*
21122112
** Remove all HTML markup from the input text. The output written into
21132113
** pOut is pure text.
2114
+**
2115
+** Put the title on the first line, if there is any <title> markup.
2116
+** If there is no <title>, then create a blank first line.
21142117
*/
21152118
void html_to_plaintext(const char *zIn, Blob *pOut){
21162119
int n;
21172120
int i, j;
2121
+ int inTitle = 0; /* True between <title>...</title> */
2122
+ int seenText = 0; /* True after first non-whitespace seen */
21182123
int nNL = 0; /* Number of \n characters at the end of pOut */
21192124
int nWS = 0; /* True if pOut ends with whitespace */
21202125
while( fossil_isspace(zIn[0]) ) zIn++;
21212126
while( zIn[0] ){
21222127
n = nextHtmlToken(zIn);
@@ -2140,22 +2145,30 @@
21402145
zIn += n;
21412146
}
21422147
if( zIn[0]=='<' ) zIn += n;
21432148
continue;
21442149
}
2145
- if( !isCloseTag && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
2150
+ if( eTag==MARKUP_TITLE ){
2151
+ inTitle = !isCloseTag;
2152
+ }
2153
+ if( !isCloseTag && seenText && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
21462154
if( nNL==0 ){
21472155
blob_append(pOut, "\n", 1);
21482156
nNL++;
21492157
}
21502158
nWS = 1;
21512159
}
21522160
}else if( fossil_isspace(zIn[0]) ){
2153
- for(i=nNL=0; i<n; i++) if( zIn[i]=='\n' ) nNL++;
2154
- if( !nWS ){
2155
- blob_append(pOut, nNL ? "\n" : " ", 1);
2156
- nWS = 1;
2161
+ if( seenText ){
2162
+ nNL = 0;
2163
+ if( !inTitle ){ /* '\n' -> ' ' within <title> */
2164
+ for(i=0; i<n; i++) if( zIn[i]=='\n' ) nNL++;
2165
+ }
2166
+ if( !nWS ){
2167
+ blob_append(pOut, nNL ? "\n" : " ", 1);
2168
+ nWS = 1;
2169
+ }
21572170
}
21582171
}else if( zIn[0]=='&' ){
21592172
char c = '?';
21602173
if( zIn[1]=='#' ){
21612174
int x = atoi(&zIn[1]);
@@ -2174,20 +2187,24 @@
21742187
break;
21752188
}
21762189
}
21772190
}
21782191
if( fossil_isspace(c) ){
2179
- if( nWS==0 ) blob_append(pOut, &c, 1);
2192
+ if( nWS==0 && seenText ) blob_append(pOut, &c, 1);
21802193
nWS = 1;
21812194
nNL = c=='\n';
21822195
}else{
2196
+ if( !seenText && !inTitle ) blob_append(pOut, "\n", 1);
2197
+ seenText = 1;
2198
+ nNL = nWS = 0;
21832199
blob_append(pOut, &c, 1);
2184
- nWS = nNL = 0;
21852200
}
21862201
}else{
2187
- blob_append(pOut, zIn, n);
2202
+ if( !seenText && !inTitle ) blob_append(pOut, "\n", 1);
2203
+ seenText = 1;
21882204
nNL = nWS = 0;
2205
+ blob_append(pOut, zIn, n);
21892206
}
21902207
zIn += n;
21912208
}
21922209
if( nNL==0 ) blob_append(pOut, "\n", 1);
21932210
}
21942211
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -2109,14 +2109,19 @@
2109 }
2110
2111 /*
2112 ** Remove all HTML markup from the input text. The output written into
2113 ** pOut is pure text.
 
 
 
2114 */
2115 void html_to_plaintext(const char *zIn, Blob *pOut){
2116 int n;
2117 int i, j;
 
 
2118 int nNL = 0; /* Number of \n characters at the end of pOut */
2119 int nWS = 0; /* True if pOut ends with whitespace */
2120 while( fossil_isspace(zIn[0]) ) zIn++;
2121 while( zIn[0] ){
2122 n = nextHtmlToken(zIn);
@@ -2140,22 +2145,30 @@
2140 zIn += n;
2141 }
2142 if( zIn[0]=='<' ) zIn += n;
2143 continue;
2144 }
2145 if( !isCloseTag && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
 
 
 
2146 if( nNL==0 ){
2147 blob_append(pOut, "\n", 1);
2148 nNL++;
2149 }
2150 nWS = 1;
2151 }
2152 }else if( fossil_isspace(zIn[0]) ){
2153 for(i=nNL=0; i<n; i++) if( zIn[i]=='\n' ) nNL++;
2154 if( !nWS ){
2155 blob_append(pOut, nNL ? "\n" : " ", 1);
2156 nWS = 1;
 
 
 
 
 
2157 }
2158 }else if( zIn[0]=='&' ){
2159 char c = '?';
2160 if( zIn[1]=='#' ){
2161 int x = atoi(&zIn[1]);
@@ -2174,20 +2187,24 @@
2174 break;
2175 }
2176 }
2177 }
2178 if( fossil_isspace(c) ){
2179 if( nWS==0 ) blob_append(pOut, &c, 1);
2180 nWS = 1;
2181 nNL = c=='\n';
2182 }else{
 
 
 
2183 blob_append(pOut, &c, 1);
2184 nWS = nNL = 0;
2185 }
2186 }else{
2187 blob_append(pOut, zIn, n);
 
2188 nNL = nWS = 0;
 
2189 }
2190 zIn += n;
2191 }
2192 if( nNL==0 ) blob_append(pOut, "\n", 1);
2193 }
2194
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -2109,14 +2109,19 @@
2109 }
2110
2111 /*
2112 ** Remove all HTML markup from the input text. The output written into
2113 ** pOut is pure text.
2114 **
2115 ** Put the title on the first line, if there is any <title> markup.
2116 ** If there is no <title>, then create a blank first line.
2117 */
2118 void html_to_plaintext(const char *zIn, Blob *pOut){
2119 int n;
2120 int i, j;
2121 int inTitle = 0; /* True between <title>...</title> */
2122 int seenText = 0; /* True after first non-whitespace seen */
2123 int nNL = 0; /* Number of \n characters at the end of pOut */
2124 int nWS = 0; /* True if pOut ends with whitespace */
2125 while( fossil_isspace(zIn[0]) ) zIn++;
2126 while( zIn[0] ){
2127 n = nextHtmlToken(zIn);
@@ -2140,22 +2145,30 @@
2145 zIn += n;
2146 }
2147 if( zIn[0]=='<' ) zIn += n;
2148 continue;
2149 }
2150 if( eTag==MARKUP_TITLE ){
2151 inTitle = !isCloseTag;
2152 }
2153 if( !isCloseTag && seenText && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
2154 if( nNL==0 ){
2155 blob_append(pOut, "\n", 1);
2156 nNL++;
2157 }
2158 nWS = 1;
2159 }
2160 }else if( fossil_isspace(zIn[0]) ){
2161 if( seenText ){
2162 nNL = 0;
2163 if( !inTitle ){ /* '\n' -> ' ' within <title> */
2164 for(i=0; i<n; i++) if( zIn[i]=='\n' ) nNL++;
2165 }
2166 if( !nWS ){
2167 blob_append(pOut, nNL ? "\n" : " ", 1);
2168 nWS = 1;
2169 }
2170 }
2171 }else if( zIn[0]=='&' ){
2172 char c = '?';
2173 if( zIn[1]=='#' ){
2174 int x = atoi(&zIn[1]);
@@ -2174,20 +2187,24 @@
2187 break;
2188 }
2189 }
2190 }
2191 if( fossil_isspace(c) ){
2192 if( nWS==0 && seenText ) blob_append(pOut, &c, 1);
2193 nWS = 1;
2194 nNL = c=='\n';
2195 }else{
2196 if( !seenText && !inTitle ) blob_append(pOut, "\n", 1);
2197 seenText = 1;
2198 nNL = nWS = 0;
2199 blob_append(pOut, &c, 1);
 
2200 }
2201 }else{
2202 if( !seenText && !inTitle ) blob_append(pOut, "\n", 1);
2203 seenText = 1;
2204 nNL = nWS = 0;
2205 blob_append(pOut, zIn, n);
2206 }
2207 zIn += n;
2208 }
2209 if( nNL==0 ) blob_append(pOut, "\n", 1);
2210 }
2211

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button