| /* |
| * Online help index routines for CUPS. |
| * |
| * Copyright © 2007-2019 by Apple Inc. |
| * Copyright © 1997-2007 by Easy Software Products. |
| * |
| * Licensed under Apache License v2.0. See the file "LICENSE" for more |
| * information. |
| */ |
| |
| /* |
| * Include necessary headers... |
| */ |
| |
| #include "cgi-private.h" |
| #include <cups/dir.h> |
| |
| |
| /* |
| * List of common English words that should not be indexed... |
| */ |
| |
| static char help_common_words[][6] = |
| { |
| "about", |
| "all", |
| "an", |
| "and", |
| "are", |
| "as", |
| "at", |
| "be", |
| "been", |
| "but", |
| "by", |
| "call", |
| "can", |
| "come", |
| "could", |
| "day", |
| "did", |
| "do", |
| "down", |
| "each", |
| "find", |
| "first", |
| "for", |
| "from", |
| "go", |
| "had", |
| "has", |
| "have", |
| "he", |
| "her", |
| "him", |
| "his", |
| "hot", |
| "how", |
| "if", |
| "in", |
| "is", |
| "it", |
| "know", |
| "like", |
| "long", |
| "look", |
| "make", |
| "many", |
| "may", |
| "more", |
| "most", |
| "my", |
| "no", |
| "now", |
| "of", |
| "on", |
| "one", |
| "or", |
| "other", |
| "out", |
| "over", |
| "said", |
| "see", |
| "she", |
| "side", |
| "so", |
| "some", |
| "sound", |
| "than", |
| "that", |
| "the", |
| "their", |
| "them", |
| "then", |
| "there", |
| "these", |
| "they", |
| "thing", |
| "this", |
| "time", |
| "to", |
| "two", |
| "up", |
| "use", |
| "was", |
| "water", |
| "way", |
| "we", |
| "were", |
| "what", |
| "when", |
| "which", |
| "who", |
| "will", |
| "with", |
| "word", |
| "would", |
| "write", |
| "you", |
| "your" |
| }; |
| |
| |
| /* |
| * Local functions... |
| */ |
| |
| static help_word_t *help_add_word(help_node_t *n, const char *text); |
| static void help_delete_node(help_node_t *n); |
| static void help_delete_word(help_word_t *w); |
| static int help_load_directory(help_index_t *hi, |
| const char *directory, |
| const char *relative); |
| static int help_load_file(help_index_t *hi, |
| const char *filename, |
| const char *relative, |
| time_t mtime); |
| static help_node_t *help_new_node(const char *filename, const char *anchor, const char *section, const char *text, time_t mtime, off_t offset, size_t length) _CUPS_NONNULL(1,3,4); |
| static int help_sort_by_name(help_node_t *p1, help_node_t *p2); |
| static int help_sort_by_score(help_node_t *p1, help_node_t *p2); |
| static int help_sort_words(help_word_t *w1, help_word_t *w2); |
| |
| |
| /* |
| * 'helpDeleteIndex()' - Delete an index, freeing all memory used. |
| */ |
| |
| void |
| helpDeleteIndex(help_index_t *hi) /* I - Help index */ |
| { |
| help_node_t *node; /* Current node */ |
| |
| |
| if (!hi) |
| return; |
| |
| for (node = (help_node_t *)cupsArrayFirst(hi->nodes); |
| node; |
| node = (help_node_t *)cupsArrayNext(hi->nodes)) |
| { |
| if (!hi->search) |
| help_delete_node(node); |
| } |
| |
| cupsArrayDelete(hi->nodes); |
| cupsArrayDelete(hi->sorted); |
| |
| free(hi); |
| } |
| |
| |
| /* |
| * 'helpFindNode()' - Find a node in an index. |
| */ |
| |
| help_node_t * /* O - Node pointer or NULL */ |
| helpFindNode(help_index_t *hi, /* I - Index */ |
| const char *filename, /* I - Filename */ |
| const char *anchor) /* I - Anchor */ |
| { |
| help_node_t key; /* Search key */ |
| |
| |
| /* |
| * Range check input... |
| */ |
| |
| if (!hi || !filename) |
| return (NULL); |
| |
| /* |
| * Initialize the search key... |
| */ |
| |
| key.filename = (char *)filename; |
| key.anchor = (char *)anchor; |
| |
| /* |
| * Return any match... |
| */ |
| |
| return ((help_node_t *)cupsArrayFind(hi->nodes, &key)); |
| } |
| |
| |
| /* |
| * 'helpLoadIndex()' - Load a help index from disk. |
| */ |
| |
| help_index_t * /* O - Index pointer or NULL */ |
| helpLoadIndex(const char *hifile, /* I - Index filename */ |
| const char *directory) /* I - Directory that is indexed */ |
| { |
| help_index_t *hi; /* Help index */ |
| cups_file_t *fp; /* Current file */ |
| char line[2048], /* Line from file */ |
| *ptr, /* Pointer into line */ |
| *filename, /* Filename in line */ |
| *anchor, /* Anchor in line */ |
| *sectptr, /* Section pointer in line */ |
| section[1024], /* Section name */ |
| *text; /* Text in line */ |
| time_t mtime; /* Modification time */ |
| off_t offset; /* Offset into file */ |
| size_t length; /* Length in bytes */ |
| int update; /* Update? */ |
| help_node_t *node; /* Current node */ |
| help_word_t *word; /* Current word */ |
| |
| |
| /* |
| * Create a new, empty index. |
| */ |
| |
| if ((hi = (help_index_t *)calloc(1, sizeof(help_index_t))) == NULL) |
| return (NULL); |
| |
| hi->nodes = cupsArrayNew((cups_array_func_t)help_sort_by_name, NULL); |
| hi->sorted = cupsArrayNew((cups_array_func_t)help_sort_by_score, NULL); |
| |
| if (!hi->nodes || !hi->sorted) |
| { |
| cupsArrayDelete(hi->nodes); |
| cupsArrayDelete(hi->sorted); |
| free(hi); |
| return (NULL); |
| } |
| |
| /* |
| * Try loading the existing index file... |
| */ |
| |
| if ((fp = cupsFileOpen(hifile, "r")) != NULL) |
| { |
| /* |
| * Lock the file and then read the first line... |
| */ |
| |
| cupsFileLock(fp, 1); |
| |
| if (cupsFileGets(fp, line, sizeof(line)) && !strcmp(line, "HELPV2")) |
| { |
| /* |
| * Got a valid header line, now read the data lines... |
| */ |
| |
| node = NULL; |
| |
| while (cupsFileGets(fp, line, sizeof(line))) |
| { |
| /* |
| * Each line looks like one of the following: |
| * |
| * filename mtime offset length "section" "text" |
| * filename#anchor offset length "text" |
| * SP count word |
| */ |
| |
| if (line[0] == ' ') |
| { |
| /* |
| * Read a word in the current node... |
| */ |
| |
| if (!node || (ptr = strrchr(line, ' ')) == NULL) |
| continue; |
| |
| if ((word = help_add_word(node, ptr + 1)) != NULL) |
| word->count = atoi(line + 1); |
| } |
| else |
| { |
| /* |
| * Add a node... |
| */ |
| |
| filename = line; |
| |
| if ((ptr = strchr(line, ' ')) == NULL) |
| break; |
| |
| while (isspace(*ptr & 255)) |
| *ptr++ = '\0'; |
| |
| if ((anchor = strrchr(filename, '#')) != NULL) |
| { |
| *anchor++ = '\0'; |
| mtime = 0; |
| } |
| else |
| mtime = strtol(ptr, &ptr, 10); |
| |
| offset = strtoll(ptr, &ptr, 10); |
| length = (size_t)strtoll(ptr, &ptr, 10); |
| |
| while (isspace(*ptr & 255)) |
| ptr ++; |
| |
| if (!anchor) |
| { |
| /* |
| * Get section... |
| */ |
| |
| if (*ptr != '\"') |
| break; |
| |
| ptr ++; |
| sectptr = ptr; |
| |
| while (*ptr && *ptr != '\"') |
| ptr ++; |
| |
| if (*ptr != '\"') |
| break; |
| |
| *ptr++ = '\0'; |
| |
| strlcpy(section, sectptr, sizeof(section)); |
| |
| while (isspace(*ptr & 255)) |
| ptr ++; |
| } |
| else |
| section[0] = '\0'; |
| |
| if (*ptr != '\"') |
| break; |
| |
| ptr ++; |
| text = ptr; |
| |
| while (*ptr && *ptr != '\"') |
| ptr ++; |
| |
| if (*ptr != '\"') |
| break; |
| |
| *ptr++ = '\0'; |
| |
| if ((node = help_new_node(filename, anchor, section, text, |
| mtime, offset, length)) == NULL) |
| break; |
| |
| node->score = -1; |
| |
| cupsArrayAdd(hi->nodes, node); |
| } |
| } |
| } |
| |
| cupsFileClose(fp); |
| } |
| |
| /* |
| * Scan for new/updated files... |
| */ |
| |
| update = help_load_directory(hi, directory, NULL); |
| |
| /* |
| * Remove any files that are no longer installed... |
| */ |
| |
| for (node = (help_node_t *)cupsArrayFirst(hi->nodes); |
| node; |
| node = (help_node_t *)cupsArrayNext(hi->nodes)) |
| if (node->score < 0) |
| { |
| /* |
| * Delete this node... |
| */ |
| |
| cupsArrayRemove(hi->nodes, node); |
| help_delete_node(node); |
| } |
| |
| /* |
| * Add nodes to the sorted array... |
| */ |
| |
| for (node = (help_node_t *)cupsArrayFirst(hi->nodes); |
| node; |
| node = (help_node_t *)cupsArrayNext(hi->nodes)) |
| cupsArrayAdd(hi->sorted, node); |
| |
| /* |
| * Save the index if we updated it... |
| */ |
| |
| if (update) |
| helpSaveIndex(hi, hifile); |
| |
| /* |
| * Return the index... |
| */ |
| |
| return (hi); |
| } |
| |
| |
| /* |
| * 'helpSaveIndex()' - Save a help index to disk. |
| */ |
| |
| int /* O - 0 on success, -1 on error */ |
| helpSaveIndex(help_index_t *hi, /* I - Index */ |
| const char *hifile) /* I - Index filename */ |
| { |
| cups_file_t *fp; /* Index file */ |
| help_node_t *node; /* Current node */ |
| help_word_t *word; /* Current word */ |
| |
| |
| /* |
| * Try creating a new index file... |
| */ |
| |
| if ((fp = cupsFileOpen(hifile, "w9")) == NULL) |
| return (-1); |
| |
| /* |
| * Lock the file while we write it... |
| */ |
| |
| cupsFileLock(fp, 1); |
| |
| cupsFilePuts(fp, "HELPV2\n"); |
| |
| for (node = (help_node_t *)cupsArrayFirst(hi->nodes); |
| node; |
| node = (help_node_t *)cupsArrayNext(hi->nodes)) |
| { |
| /* |
| * Write the current node with/without the anchor... |
| */ |
| |
| if (node->anchor) |
| { |
| if (cupsFilePrintf(fp, "%s#%s " CUPS_LLFMT " " CUPS_LLFMT " \"%s\"\n", |
| node->filename, node->anchor, |
| CUPS_LLCAST node->offset, CUPS_LLCAST node->length, |
| node->text) < 0) |
| break; |
| } |
| else |
| { |
| if (cupsFilePrintf(fp, "%s %d " CUPS_LLFMT " " CUPS_LLFMT " \"%s\" \"%s\"\n", |
| node->filename, (int)node->mtime, |
| CUPS_LLCAST node->offset, CUPS_LLCAST node->length, |
| node->section ? node->section : "", node->text) < 0) |
| break; |
| } |
| |
| /* |
| * Then write the words associated with the node... |
| */ |
| |
| for (word = (help_word_t *)cupsArrayFirst(node->words); |
| word; |
| word = (help_word_t *)cupsArrayNext(node->words)) |
| if (cupsFilePrintf(fp, " %d %s\n", word->count, word->text) < 0) |
| break; |
| } |
| |
| cupsFileFlush(fp); |
| |
| if (cupsFileClose(fp) < 0) |
| return (-1); |
| else if (node) |
| return (-1); |
| else |
| return (0); |
| } |
| |
| |
| /* |
| * 'helpSearchIndex()' - Search an index. |
| */ |
| |
| help_index_t * /* O - Search index */ |
| helpSearchIndex(help_index_t *hi, /* I - Index */ |
| const char *query, /* I - Query string */ |
| const char *section, /* I - Limit search to this section */ |
| const char *filename) /* I - Limit search to this file */ |
| { |
| help_index_t *search; /* Search index */ |
| help_node_t *node; /* Current node */ |
| help_word_t *word; /* Current word */ |
| void *sc; /* Search context */ |
| int matches; /* Number of matches */ |
| |
| |
| /* |
| * Range check... |
| */ |
| |
| if (!hi || !query) |
| return (NULL); |
| |
| /* |
| * Reset the scores of all nodes to 0... |
| */ |
| |
| for (node = (help_node_t *)cupsArrayFirst(hi->nodes); |
| node; |
| node = (help_node_t *)cupsArrayNext(hi->nodes)) |
| node->score = 0; |
| |
| /* |
| * Find the first node to search in... |
| */ |
| |
| if (filename) |
| { |
| node = helpFindNode(hi, filename, NULL); |
| if (!node) |
| return (NULL); |
| } |
| else |
| node = (help_node_t *)cupsArrayFirst(hi->nodes); |
| |
| /* |
| * Convert the query into a regular expression... |
| */ |
| |
| sc = cgiCompileSearch(query); |
| if (!sc) |
| return (NULL); |
| |
| /* |
| * Allocate a search index... |
| */ |
| |
| search = calloc(1, sizeof(help_index_t)); |
| if (!search) |
| { |
| cgiFreeSearch(sc); |
| return (NULL); |
| } |
| |
| search->nodes = cupsArrayNew((cups_array_func_t)help_sort_by_name, NULL); |
| search->sorted = cupsArrayNew((cups_array_func_t)help_sort_by_score, NULL); |
| |
| if (!search->nodes || !search->sorted) |
| { |
| cupsArrayDelete(search->nodes); |
| cupsArrayDelete(search->sorted); |
| free(search); |
| cgiFreeSearch(sc); |
| return (NULL); |
| } |
| |
| search->search = 1; |
| |
| /* |
| * Check each node in the index, adding matching nodes to the |
| * search index... |
| */ |
| |
| for (; node; node = (help_node_t *)cupsArrayNext(hi->nodes)) |
| if (section && strcmp(node->section, section)) |
| continue; |
| else if (filename && strcmp(node->filename, filename)) |
| continue; |
| else |
| { |
| matches = cgiDoSearch(sc, node->text); |
| |
| for (word = (help_word_t *)cupsArrayFirst(node->words); |
| word; |
| word = (help_word_t *)cupsArrayNext(node->words)) |
| if (cgiDoSearch(sc, word->text) > 0) |
| matches += word->count; |
| |
| if (matches > 0) |
| { |
| /* |
| * Found a match, add the node to the search index... |
| */ |
| |
| node->score = matches; |
| |
| cupsArrayAdd(search->nodes, node); |
| cupsArrayAdd(search->sorted, node); |
| } |
| } |
| |
| /* |
| * Free the search context... |
| */ |
| |
| cgiFreeSearch(sc); |
| |
| /* |
| * Return the results... |
| */ |
| |
| return (search); |
| } |
| |
| |
| /* |
| * 'help_add_word()' - Add a word to a node. |
| */ |
| |
| static help_word_t * /* O - New word */ |
| help_add_word(help_node_t *n, /* I - Node */ |
| const char *text) /* I - Word text */ |
| { |
| help_word_t *w, /* New word */ |
| key; /* Search key */ |
| |
| |
| /* |
| * Create the words array as needed... |
| */ |
| |
| if (!n->words) |
| n->words = cupsArrayNew((cups_array_func_t)help_sort_words, NULL); |
| |
| /* |
| * See if the word is already added... |
| */ |
| |
| key.text = (char *)text; |
| |
| if ((w = (help_word_t *)cupsArrayFind(n->words, &key)) == NULL) |
| { |
| /* |
| * Create a new word... |
| */ |
| |
| if ((w = calloc(1, sizeof(help_word_t))) == NULL) |
| return (NULL); |
| |
| if ((w->text = strdup(text)) == NULL) |
| { |
| free(w); |
| return (NULL); |
| } |
| |
| cupsArrayAdd(n->words, w); |
| } |
| |
| /* |
| * Bump the counter for this word and return it... |
| */ |
| |
| w->count ++; |
| |
| return (w); |
| } |
| |
| |
| /* |
| * 'help_delete_node()' - Free all memory used by a node. |
| */ |
| |
| static void |
| help_delete_node(help_node_t *n) /* I - Node */ |
| { |
| help_word_t *w; /* Current word */ |
| |
| |
| if (!n) |
| return; |
| |
| if (n->filename) |
| free(n->filename); |
| |
| if (n->anchor) |
| free(n->anchor); |
| |
| if (n->section) |
| free(n->section); |
| |
| if (n->text) |
| free(n->text); |
| |
| for (w = (help_word_t *)cupsArrayFirst(n->words); |
| w; |
| w = (help_word_t *)cupsArrayNext(n->words)) |
| help_delete_word(w); |
| |
| cupsArrayDelete(n->words); |
| |
| free(n); |
| } |
| |
| |
| /* |
| * 'help_delete_word()' - Free all memory used by a word. |
| */ |
| |
| static void |
| help_delete_word(help_word_t *w) /* I - Word */ |
| { |
| if (!w) |
| return; |
| |
| if (w->text) |
| free(w->text); |
| |
| free(w); |
| } |
| |
| |
| /* |
| * 'help_load_directory()' - Load a directory of files into an index. |
| */ |
| |
| static int /* O - 0 = success, -1 = error, 1 = updated */ |
| help_load_directory( |
| help_index_t *hi, /* I - Index */ |
| const char *directory, /* I - Directory */ |
| const char *relative) /* I - Relative path */ |
| { |
| cups_dir_t *dir; /* Directory file */ |
| cups_dentry_t *dent; /* Directory entry */ |
| char *ext, /* Pointer to extension */ |
| filename[1024], /* Full filename */ |
| relname[1024]; /* Relative filename */ |
| int update; /* Updated? */ |
| help_node_t *node; /* Current node */ |
| |
| |
| /* |
| * Open the directory and scan it... |
| */ |
| |
| if ((dir = cupsDirOpen(directory)) == NULL) |
| return (0); |
| |
| update = 0; |
| |
| while ((dent = cupsDirRead(dir)) != NULL) |
| { |
| /* |
| * Skip "." files... |
| */ |
| |
| if (dent->filename[0] == '.') |
| continue; |
| |
| /* |
| * Get absolute and relative filenames... |
| */ |
| |
| snprintf(filename, sizeof(filename), "%s/%s", directory, dent->filename); |
| if (relative) |
| snprintf(relname, sizeof(relname), "%s/%s", relative, dent->filename); |
| else |
| strlcpy(relname, dent->filename, sizeof(relname)); |
| |
| /* |
| * Check if we have a HTML file... |
| */ |
| |
| if ((ext = strstr(dent->filename, ".html")) != NULL && |
| (!ext[5] || !strcmp(ext + 5, ".gz"))) |
| { |
| /* |
| * HTML file, see if we have already indexed the file... |
| */ |
| |
| if ((node = helpFindNode(hi, relname, NULL)) != NULL) |
| { |
| /* |
| * File already indexed - check dates to confirm that the |
| * index is up-to-date... |
| */ |
| |
| if (node->mtime == dent->fileinfo.st_mtime) |
| { |
| /* |
| * Same modification time, so mark all of the nodes |
| * for this file as up-to-date... |
| */ |
| |
| for (; node; node = (help_node_t *)cupsArrayNext(hi->nodes)) |
| if (!strcmp(node->filename, relname)) |
| node->score = 0; |
| else |
| break; |
| |
| continue; |
| } |
| } |
| |
| update = 1; |
| |
| help_load_file(hi, filename, relname, dent->fileinfo.st_mtime); |
| } |
| else if (S_ISDIR(dent->fileinfo.st_mode)) |
| { |
| /* |
| * Process sub-directory... |
| */ |
| |
| if (help_load_directory(hi, filename, relname) == 1) |
| update = 1; |
| } |
| } |
| |
| cupsDirClose(dir); |
| |
| return (update); |
| } |
| |
| |
| /* |
| * 'help_load_file()' - Load a HTML files into an index. |
| */ |
| |
| static int /* O - 0 = success, -1 = error */ |
| help_load_file( |
| help_index_t *hi, /* I - Index */ |
| const char *filename, /* I - Filename */ |
| const char *relative, /* I - Relative path */ |
| time_t mtime) /* I - Modification time */ |
| { |
| cups_file_t *fp; /* HTML file */ |
| help_node_t *node; /* Current node */ |
| char line[1024], /* Line from file */ |
| temp[1024], /* Temporary word */ |
| section[1024], /* Section */ |
| *ptr, /* Pointer into line */ |
| *anchor, /* Anchor name */ |
| *text; /* Text for anchor */ |
| off_t offset; /* File offset */ |
| char quote; /* Quote character */ |
| help_word_t *word; /* Current word */ |
| int wordlen; /* Length of word */ |
| |
| |
| if ((fp = cupsFileOpen(filename, "r")) == NULL) |
| return (-1); |
| |
| node = NULL; |
| offset = 0; |
| |
| strlcpy(section, "Other", sizeof(section)); |
| |
| while (cupsFileGets(fp, line, sizeof(line))) |
| { |
| /* |
| * Look for "<TITLE>", "<A NAME", or "<!-- SECTION:" prefix... |
| */ |
| |
| if ((ptr = strstr(line, "<!-- SECTION:")) != NULL) |
| { |
| /* |
| * Got section line, copy it! |
| */ |
| |
| for (ptr += 13; isspace(*ptr & 255); ptr ++); |
| |
| strlcpy(section, ptr, sizeof(section)); |
| if ((ptr = strstr(section, "-->")) != NULL) |
| { |
| /* |
| * Strip comment stuff from end of line... |
| */ |
| |
| for (*ptr-- = '\0'; ptr > line && isspace(*ptr & 255); *ptr-- = '\0'); |
| |
| if (isspace(*ptr & 255)) |
| *ptr = '\0'; |
| } |
| continue; |
| } |
| |
| for (ptr = line; (ptr = strchr(ptr, '<')) != NULL;) |
| { |
| ptr ++; |
| |
| if (!_cups_strncasecmp(ptr, "TITLE>", 6)) |
| { |
| /* |
| * Found the title... |
| */ |
| |
| anchor = NULL; |
| ptr += 6; |
| } |
| else |
| { |
| char *idptr; /* Pointer to ID */ |
| |
| if (!_cups_strncasecmp(ptr, "A NAME=", 7)) |
| ptr += 7; |
| else if ((idptr = strstr(ptr, " ID=")) != NULL) |
| ptr = idptr + 4; |
| else if ((idptr = strstr(ptr, " id=")) != NULL) |
| ptr = idptr + 4; |
| else |
| continue; |
| |
| /* |
| * Found an anchor... |
| */ |
| |
| if (*ptr == '\"' || *ptr == '\'') |
| { |
| /* |
| * Get quoted anchor... |
| */ |
| |
| quote = *ptr; |
| anchor = ptr + 1; |
| if ((ptr = strchr(anchor, quote)) != NULL) |
| *ptr++ = '\0'; |
| else |
| break; |
| } |
| else |
| { |
| /* |
| * Get unquoted anchor... |
| */ |
| |
| anchor = ptr + 1; |
| |
| for (ptr = anchor; *ptr && *ptr != '>' && !isspace(*ptr & 255); ptr ++); |
| |
| if (*ptr != '>') |
| *ptr++ = '\0'; |
| else |
| break; |
| } |
| |
| /* |
| * Got the anchor, now lets find the end... |
| */ |
| |
| while (*ptr && *ptr != '>') |
| ptr ++; |
| |
| if (*ptr != '>') |
| break; |
| |
| *ptr++ = '\0'; |
| } |
| |
| /* |
| * Now collect text for the link... |
| */ |
| |
| text = ptr; |
| while ((ptr = strchr(text, '<')) == NULL) |
| { |
| ptr = text + strlen(text); |
| if (ptr >= (line + sizeof(line) - 2)) |
| break; |
| |
| *ptr++ = ' '; |
| |
| if (!cupsFileGets(fp, ptr, sizeof(line) - (size_t)(ptr - line) - 1)) |
| break; |
| } |
| |
| *ptr = '\0'; |
| |
| if (node) |
| node->length = (size_t)(offset - node->offset); |
| |
| if (!*text) |
| { |
| node = NULL; |
| break; |
| } |
| |
| if ((node = helpFindNode(hi, relative, anchor)) != NULL) |
| { |
| /* |
| * Node already in the index, so replace the text and other |
| * data... |
| */ |
| |
| cupsArrayRemove(hi->nodes, node); |
| |
| if (node->section) |
| free(node->section); |
| |
| if (node->text) |
| free(node->text); |
| |
| if (node->words) |
| { |
| for (word = (help_word_t *)cupsArrayFirst(node->words); |
| word; |
| word = (help_word_t *)cupsArrayNext(node->words)) |
| help_delete_word(word); |
| |
| cupsArrayDelete(node->words); |
| node->words = NULL; |
| } |
| |
| node->section = section[0] ? strdup(section) : NULL; |
| node->text = strdup(text); |
| node->mtime = mtime; |
| node->offset = offset; |
| node->score = 0; |
| } |
| else |
| { |
| /* |
| * New node... |
| */ |
| |
| node = help_new_node(relative, anchor, section, text, mtime, offset, 0); |
| } |
| |
| /* |
| * Go through the text value and replace tabs and newlines with |
| * whitespace and eliminate extra whitespace... |
| */ |
| |
| for (ptr = node->text, text = node->text; *ptr;) |
| if (isspace(*ptr & 255)) |
| { |
| while (isspace(*ptr & 255)) |
| ptr ++; |
| |
| *text++ = ' '; |
| } |
| else if (text != ptr) |
| *text++ = *ptr++; |
| else |
| { |
| text ++; |
| ptr ++; |
| } |
| |
| *text = '\0'; |
| |
| /* |
| * (Re)add the node to the array... |
| */ |
| |
| cupsArrayAdd(hi->nodes, node); |
| |
| if (!anchor) |
| node = NULL; |
| break; |
| } |
| |
| if (node) |
| { |
| /* |
| * Scan this line for words... |
| */ |
| |
| for (ptr = line; *ptr; ptr ++) |
| { |
| /* |
| * Skip HTML stuff... |
| */ |
| |
| if (*ptr == '<') |
| { |
| if (!strncmp(ptr, "<!--", 4)) |
| { |
| /* |
| * Skip HTML comment... |
| */ |
| |
| if ((text = strstr(ptr + 4, "-->")) == NULL) |
| ptr += strlen(ptr) - 1; |
| else |
| ptr = text + 2; |
| } |
| else |
| { |
| /* |
| * Skip HTML element... |
| */ |
| |
| for (ptr ++; *ptr && *ptr != '>'; ptr ++) |
| { |
| if (*ptr == '\"' || *ptr == '\'') |
| { |
| for (quote = *ptr++; *ptr && *ptr != quote; ptr ++); |
| |
| if (!*ptr) |
| ptr --; |
| } |
| } |
| |
| if (!*ptr) |
| ptr --; |
| } |
| |
| continue; |
| } |
| else if (*ptr == '&') |
| { |
| /* |
| * Skip HTML entity... |
| */ |
| |
| for (ptr ++; *ptr && *ptr != ';'; ptr ++); |
| |
| if (!*ptr) |
| ptr --; |
| |
| continue; |
| } |
| else if (!isalnum(*ptr & 255)) |
| continue; |
| |
| /* |
| * Found the start of a word, search until we find the end... |
| */ |
| |
| for (text = ptr, ptr ++; *ptr && isalnum(*ptr & 255); ptr ++); |
| |
| wordlen = (int)(ptr - text); |
| |
| memcpy(temp, text, (size_t)wordlen); |
| temp[wordlen] = '\0'; |
| |
| ptr --; |
| |
| if (wordlen > 1 && !bsearch(temp, help_common_words, |
| (sizeof(help_common_words) / |
| sizeof(help_common_words[0])), |
| sizeof(help_common_words[0]), |
| (int (*)(const void *, const void *)) |
| _cups_strcasecmp)) |
| help_add_word(node, temp); |
| } |
| } |
| |
| /* |
| * Get the offset of the next line... |
| */ |
| |
| offset = cupsFileTell(fp); |
| } |
| |
| cupsFileClose(fp); |
| |
| if (node) |
| node->length = (size_t)(offset - node->offset); |
| |
| return (0); |
| } |
| |
| |
| /* |
| * 'help_new_node()' - Create a new node and add it to an index. |
| */ |
| |
| static help_node_t * /* O - Node pointer or NULL on error */ |
| help_new_node(const char *filename, /* I - Filename */ |
| const char *anchor, /* I - Anchor */ |
| const char *section, /* I - Section */ |
| const char *text, /* I - Text */ |
| time_t mtime, /* I - Modification time */ |
| off_t offset, /* I - Offset in file */ |
| size_t length) /* I - Length in bytes */ |
| { |
| help_node_t *n; /* Node */ |
| |
| |
| n = (help_node_t *)calloc(1, sizeof(help_node_t)); |
| if (!n) |
| return (NULL); |
| |
| n->filename = strdup(filename); |
| n->anchor = anchor ? strdup(anchor) : NULL; |
| n->section = (section && *section) ? strdup(section) : NULL; |
| n->text = strdup(text); |
| n->mtime = mtime; |
| n->offset = offset; |
| n->length = length; |
| |
| return (n); |
| } |
| |
| |
| /* |
| * 'help_sort_nodes_by_name()' - Sort nodes by section, filename, and anchor. |
| */ |
| |
| static int /* O - Difference */ |
| help_sort_by_name(help_node_t *n1, /* I - First node */ |
| help_node_t *n2) /* I - Second node */ |
| { |
| int diff; /* Difference */ |
| |
| |
| if ((diff = strcmp(n1->filename, n2->filename)) != 0) |
| return (diff); |
| |
| if (!n1->anchor && !n2->anchor) |
| return (0); |
| else if (!n1->anchor) |
| return (-1); |
| else if (!n2->anchor) |
| return (1); |
| else |
| return (strcmp(n1->anchor, n2->anchor)); |
| } |
| |
| |
| /* |
| * 'help_sort_nodes_by_score()' - Sort nodes by score and text. |
| */ |
| |
| static int /* O - Difference */ |
| help_sort_by_score(help_node_t *n1, /* I - First node */ |
| help_node_t *n2) /* I - Second node */ |
| { |
| int diff; /* Difference */ |
| |
| |
| if (n1->score != n2->score) |
| return (n2->score - n1->score); |
| |
| if (n1->section && !n2->section) |
| return (1); |
| else if (!n1->section && n2->section) |
| return (-1); |
| else if (n1->section && n2->section && |
| (diff = strcmp(n1->section, n2->section)) != 0) |
| return (diff); |
| |
| return (_cups_strcasecmp(n1->text, n2->text)); |
| } |
| |
| |
| /* |
| * 'help_sort_words()' - Sort words alphabetically. |
| */ |
| |
| static int /* O - Difference */ |
| help_sort_words(help_word_t *w1, /* I - Second word */ |
| help_word_t *w2) /* I - Second word */ |
| { |
| return (_cups_strcasecmp(w1->text, w2->text)); |
| } |