/*--[explode.c]---------------------------------------------------------------- | Copyright (C) 2002, 2003 Dan A. Jackson | | This file is part of "clit" (Convert LIT). | | "clit" is free software; you can redistribute it and/or modify | it under the terms of the GNU General Public License as published by | the Free Software Foundation; either version 2 of the License, or | (at your option) any later version. | | This program is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General Public License for more details. | | You should have received a copy of the GNU General Public License | along with this program; if not, write to the Free Software | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | | The GNU General Public License may also be available at the following | URL: http://www.gnu.org/licenses/gpl.html */ #include #include #include #include #include "clit.h" #include "litlib.h" #include "manifest.h" #include "utils.h" #ifdef _MSC_VER #include #define mkdir(x,y) _mkdir(x) #define strncasecmp _strnicmp #endif /* | Herein is the routines to handle "exploding" of .LIT files into their | component pieces. | Some features, most noteably parsing the "/manifest" and expanding the | tag-compressed HTML are included in the library. */ extern int disable_directory_support; manifest_type manifest; extern char * writingFilename; /* Leave off the newline intentionally */ const char * meta_string = "\n\ "; const char * xhtml_string = "\n\ "; int directory_flag = 0; int create_placeholders(char * pathOutput,manifest_type * pManifest); char * get_opf_name(char * pathOutput, char * litname); int write_htmlish_file(lit_file * litfile, char *, char *, char *); int write_raw_file(lit_file * litfile,char *pathInternal, char *pathExternal); int create_full_path(char * pathStarting, char * pathFile); int explode_lit(lit_file * litfile, char * litName, char * pathOutput) { char * pathOPF = NULL; char * pathInternal = NULL; char * path = NULL; int status; int stage, i; status = lit_read_manifest(litfile, &manifest); if (status) goto bad; status = create_placeholders(pathOutput,&manifest); if (status) goto bad; pathOPF = get_opf_name(pathOutput,litName); writingFilename = pathOPF; status = write_htmlish_file(litfile, "/meta", pathOPF, NULL); free(pathOPF); pathOPF = NULL; writingFilename = NULL; if (status) goto bad; for (stage = 0; stage < NUM_MANIFEST_MAPS; stage++) { name_mapping * map; if (!manifest.mappings[stage]) continue; map = manifest.mappings[stage]; for (i = 0; i < manifest.num_mappings[stage]; i++) { path = strmerge(pathOutput,(char *)map->sOriginal,NULL); if (!path) { status = E_LIT_OUT_OF_MEMORY; goto bad; } writingFilename = path; show_information("Writing out \"%s\" as \"%s\" ...", map->sInternal, map->sOriginal); status = E_LIT_INTERNAL_ERROR; switch (stage) { case 0: case 1: pathInternal = NULL; /* Generated inside htmlish code */ status = write_htmlish_file(litfile,(char *)map->sInternal, path,(char *)map->sOriginal); break; case 2: case 3: pathInternal = strmerge("/data/",(char *)map->sInternal,0); if (!pathInternal) { status = E_LIT_OUT_OF_MEMORY; goto bad; } status = write_raw_file(litfile,pathInternal,path); break; } if (status) { if (status > 0) { show_information("Wrote out \"%s\", but with errors.\n", path); } if (status < 0) return -1; } else { show_information("Successfully written to \"%s\".\n", path); } if (path) { free(path); path = NULL;} if (pathInternal) { free(pathInternal); pathInternal = NULL; } writingFilename = NULL; map++; } } return 0; bad: if (path) free(path); if (pathOPF) free(pathOPF); if (pathInternal) free(pathInternal); return status; } /* | Return the length (in "points" not characters!) that is common between all | entries. */ int find_common_length(manifest_type * pManifest) { mapping_state state; name_mapping * map; U8 * sFirst; int common_length; mapping_init(&state, pManifest); /** * First, find the maximum number of characters that is common between * _all_ original filenames. This will be 0 except for some rare cases. */ map = mapping_next(&state); sFirst = NULL; common_length = 0; /* Common length is in UTF8 units */ if (map) { sFirst = map->sOriginal; common_length = utf8_strlen(sFirst); } while ((map = mapping_next(&state)) != NULL) { int new_common; new_common = utf8_strmatch(sFirst, map->sOriginal); if (new_common < common_length) common_length = new_common; } return common_length; } /* | Create a single file from a name mapping, | Creates directories as needed. */ U8 * create_file_path(U8 * pathOutput, U8 * pathFile) { U32 c; int error, len, status; utf8_iter iter; U8 * sFiltered, * sDirname, * sTemp, * s, * p; FILE * f; sFiltered = sTemp = sDirname = NULL; do { sFiltered = strmerge(pathFile, NULL,NULL); if (!sFiltered) return NULL; sDirname = strmerge(pathFile, NULL,NULL); if (!sDirname) return NULL; error = 0; /* Loop one.. * Filter */ utf8_start(&iter, pathFile); s = sFiltered; do { c = utf8_next(&iter); if (!c) { *s = '\0'; } /* Filter out things which wouldn't be in DOS filenames */ else if ((c < ' ')|| (c == '?')|| (c == '<')|| (c == '>')|| (c == '\"')|| (c == '*')|| (c == '|')|| (c == ';')|| (c ==0x7f) || (c == ':')) { /* By definition, this can't be UTF8! */ *(s++) = '_'; } else if ((c == '/') || (c == '\\')) { /* Use common slash, This is differentated later */ *(s++) = '/'; } else { len = utf8_store(s, &iter); s += len; } } while (c); s = sDirname; utf8_start(&iter, sFiltered); do { p = utf8_next_token(&iter, '/', &len); if (!len) break; /* Skip dots. */ if ((utf8_strmatch(p,"..") == 2) || (utf8_strmatch(p,".") == 1)) { continue; } memcpy(s, p, len); s += len; *s = '\0'; /* This isn't a directory, continue on to the file check */ if (utf8_peek(&iter) == 0) break; sTemp = strmerge(pathOutput,sDirname, NULL); if (!sTemp) { error = 9; break; } directory_flag++; if (disable_directory_support) { /* Shouldn't be here, because the other check takes * care of it. Just in case.... */ error = 11; break; } status = mkdir(sTemp,0755); if (status && (errno != EEXIST)) { lit_error(ERR_LIBC,"WARNING: mkdir() failed for \"%s\".\n", sTemp); free(sTemp); sTemp = NULL; error = 10; break; } free(sTemp); sTemp = NULL; #ifdef _MSC_VER *(s++) = '\\'; #else *(s++) = '/'; #endif } while (1); free(sFiltered); sFiltered = NULL; /* If an error occurred, go on to the next (pathless) potential name */ if (error) break; /* Str should now contain a full qualified path, try to make the * file. */ f = NULL; sTemp = strmerge(pathOutput, sDirname, NULL); if (sTemp) { f = fopen(sTemp, "r"); if (f) { error = 1; } else { f = fopen(sTemp,"w"); if (f) { fclose(f); error = 0; } else { error = 2; } } } else { error = -1; break; } } while (0); if (sTemp) free(sTemp); if (sFiltered) free(sFiltered); if (error) { if (sDirname) free(sDirname); sDirname = NULL; } return sDirname; } char * int2string(int num) { static char sNum[10]; char *sNumPtr; int temp = abs(num); sNumPtr = &sNum[sizeof(sNum)/sizeof(sNum[0]) - 1]; *(sNumPtr--) = '\0'; do { *sNumPtr = '0' + (temp%10); sNumPtr--; temp /= 10; } while (temp && (sNumPtr > sNum)); if (num < 0) *sNumPtr = '-'; else sNumPtr++; return sNumPtr; } /* | This routine is responsible for converting from the "original" names | stored in the .LIT file into names on the local file system. | | To fix duplicates, this CREATES all the files as it goes along. | It also permits recreating a directory tree. | | Steps: | 1. Filter out the common portions. | (Workaround for a buggy generator which stores the originals as: | C:\Something\Else\Books\ISBN\index.html). | | 2. Starting at the unique portion, create the subdirectories | Subdirectories aren't handled at the moment, due to relative pathing. | Filter out backwards references. | | 3. Finally, create the file. if it exists, try some numbers. | | 4. If all the numbers are taken, start over with the Internal filename | */ int create_placeholders(char * pathOutput,manifest_type * pManifest) { int units2skip; mapping_state state; name_mapping * map; units2skip = find_common_length(pManifest); /* For each entry, find the unique portion and start the games */ mapping_init(&state, pManifest); while ((map = mapping_next(&state)) != NULL) { int bytes, units, len, bytes_after_dot; U8 *p, * slash, * dot; U32 c; /* Skip to the last slash */ if (disable_directory_support) units2skip = 0x7FFFF; slash = p = map->sOriginal; bytes = strlen(p)+1; units = 0; while (units < units2skip) { len = read_utf8_char(p, bytes, &c); if ((!c) || (len <= 0)) break; if ((c == '/') || (c == '\\')) slash = (p+len); units++; p += len; } p = dot = slash; while (1) { len = read_utf8_char(p, bytes, &c); if ((!c) || (len <= 0)) break; if (c == '.') dot = p; units++; p += len; } bytes_after_dot = p - dot; if (!bytes_after_dot) { dot = NULL; } /* Start at the previous slash or the beginning */ p = create_file_path(pathOutput, slash); if (!p) { U8 * s; int count; s = strmerge(map->sInternal, dot, NULL); if (s) p = create_file_path(pathOutput, s); if (!p) { if (s) free(s); for (count = 1; count < 1000; count++) { s = strmerge(map->sInternal, int2string(-count), dot); if (s) { p = create_file_path(pathOutput,s); } if (p) break; } if (count == 1000) { lit_error(0, "Unable to create a DOS file for \"%s\". \n", map->sInternal); return -1; } } } if (p) { free(map->sOriginal); map->sOriginal = p; } } return 0; } char * get_opf_name(char * pathOutput, char * litname) { int idx, len; char * str; idx = len = strlen(litname); /* assume that litname is a valid LIT file */ while (idx) { if ((litname[idx] == '/') || (litname[idx] == '\\') || (litname[idx] == ':')) { idx++; break; } idx--; } if (strncasecmp(&litname[len-4],".lit",4) == 0) { str = strmerge(pathOutput,&litname[idx],NULL); if (!str) return str; strcpy(&str[strlen(str)-3], "opf"); return str; } return strmerge(pathOutput,&litname[idx],".opf"); } int write_callback(void * v, U8 * pData, int nBytes) { FILE * f; int r; if (!v) { f = stdout; } else { f = (FILE *)v; } r = fwrite(pData,1, nBytes,f); if (r < 0) { lit_error(ERR_W|ERR_LIBC,"fwrite of %d bytes failed!",nBytes); } return r; } int write_htmlish_file(lit_file * litfile, char * source_name, char * pathExternal, char * pathOriginal) { U8 * p; lit_atom_list * listAtoms; int nbytes; int status; FILE * fOut; char * pathInternal; manifest_type * pmanifest, relative_manifest; pmanifest = &manifest; p = NULL; fOut = NULL; listAtoms = NULL; pathInternal = NULL; status = -1; do { if (pathOriginal != NULL) { pathInternal = strmerge("/data/",source_name, "/content"); } else { /* the meta files, don't have the 3-piece string */ /* So, copy anyway - it only happens once */ pathInternal = strmerge(source_name, 0, 0 ); } if (!pathInternal) { status = E_LIT_OUT_OF_MEMORY; break; } if (pathOriginal) { listAtoms = lit_read_atoms(litfile, source_name); } status = lit_get_file(litfile,pathInternal,&p, &nbytes); if (status) { break; } if ((directory_flag) && pathOriginal && !disable_directory_support) { status = make_relative_manifest(pathOriginal,&relative_manifest, pmanifest); if (status != 0) { status = -20; break;} pmanifest = &relative_manifest; } fOut = fopen(pathExternal, "w"); if (!fOut) { lit_error(ERR_LIBC|ERR_W,"fopen(%s) failed!", pathExternal); status = -1; break; } if (pathOriginal == NULL) { status = write_callback((void *)fOut,(U8 *)meta_string, strlen(meta_string)); if ((size_t)status != strlen(meta_string)) { status = -1; break; } status = lit_reconstitute_html(p, 0, nbytes, 1, pmanifest, listAtoms, write_callback, (void *)fOut); } else { status = write_callback((void *)fOut,(U8 *)xhtml_string, strlen(xhtml_string)); if ((size_t)status != strlen(xhtml_string)) { status = -1; break;} status = lit_reconstitute_html(p, 0, nbytes, 0, pmanifest, listAtoms, write_callback, (void *)fOut); } if ((status > 0) && (status != nbytes)) { lit_error(ERR_R, "Warning - Couldn't fully expand \"%s\", decoded %d out of %d.\n", pathInternal, status, (int)nbytes); status = 1; } else if (status < 0) { lit_error(ERR_R, "ERROR - Failure during tag expansion of \"%s\" Code: %d!\n", pathInternal, status); } else status = 0; } while (0); if (fOut != NULL) fclose(fOut); if (p) free(p); if (pathInternal) free(pathInternal); if (listAtoms) { lit_free_atoms(listAtoms); } return status; } int write_raw_file(lit_file * litfile,char * pathInternal, char * pathExternal) { int status; int nbytes; U8 * p; FILE * fOut; status = lit_get_file(litfile,pathInternal,&p,&nbytes); if (status) return status; fOut = fopen(pathExternal, "wb"); if (!fOut) { free(p); lit_error(ERR_LIBC|ERR_W,"fopen(%s) failed!", pathExternal); return -1; } status = fwrite(p,1, nbytes,fOut); free(p); p = NULL; if (status < 0) { lit_error(ERR_W|ERR_LIBC,"Failed to write %d bytes.",nbytes); return status; } if (status != nbytes) { lit_error(ERR_W,"Partial write, wrote %d out of %d.",status,nbytes); return -1; } fclose(fOut); return 0; }