PageRenderTime 64ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/Tools/HTMLLinker.m

http://github.com/gnustep/gnustep-base
Objective C | 1360 lines | 928 code | 217 blank | 215 comment | 305 complexity | 893bea833f93ed44bb13526e01dc9065 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. /** The GNUstep HTML Linker
  2. <title>HTMLLinker. A tool to fix up href references in html files</title>
  3. Copyright (C) 2002,2007 Free Software Foundation, Inc.
  4. Written by: Nicola Pero <nicola@brainstorm.co.uk>
  5. Date: January 2002
  6. This file is part of the GNUstep Project
  7. This program is free software; you can redistribute it and/or
  8. modify it under the terms of the GNU General Public License
  9. as published by the Free Software Foundation; either
  10. version 3 of the License, or (at your option) any later version.
  11. You should have received a copy of the GNU General Public
  12. License along with this program; see the file COPYINGv3.
  13. If not, write to the Free Software Foundation,
  14. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  15. */
  16. /*
  17. * See the HTMLLinker.html file for documentation on how to use the tool.
  18. */
  19. #import "common.h"
  20. #import "Foundation/NSArray.h"
  21. #import "Foundation/NSAutoreleasePool.h"
  22. #import "Foundation/NSFileManager.h"
  23. #import "Foundation/NSPathUtilities.h"
  24. #import "Foundation/NSProcessInfo.h"
  25. #import "Foundation/NSUserDefaults.h"
  26. /* For convenience, cached for the whole tool. */
  27. /* [NSFileManager defaultManager] */
  28. static NSFileManager *fileManager = nil;
  29. /* [[NSFileManager defaulManager] currentDirectoryPath] */
  30. static NSString *currentPath = nil;
  31. static int verbose = 0;
  32. /* Enumerate all .html (or .htmlink) files in a directory and
  33. subdirectories. */
  34. @interface HTMLDirectoryEnumerator : NSEnumerator
  35. {
  36. NSDirectoryEnumerator *e;
  37. NSString *basePath;
  38. BOOL looksForHTMLLinkFiles;
  39. BOOL returnsAbsolutePaths;
  40. }
  41. - (id)initWithBasePath: (NSString *)path;
  42. - (void)setReturnsAbsolutePaths: (BOOL)flag;
  43. - (void)setLooksForHTMLLinkFiles: (BOOL)flag;
  44. @end
  45. @implementation HTMLDirectoryEnumerator : NSEnumerator
  46. - (id)initWithBasePath: (NSString *)path
  47. {
  48. ASSIGN (e, [fileManager enumeratorAtPath: path]);
  49. ASSIGN (basePath, path);
  50. return [super init];
  51. }
  52. - (void)dealloc
  53. {
  54. RELEASE (e);
  55. RELEASE (basePath);
  56. [super dealloc];
  57. }
  58. - (void)setReturnsAbsolutePaths: (BOOL)flag
  59. {
  60. returnsAbsolutePaths = flag;
  61. }
  62. - (void)setLooksForHTMLLinkFiles: (BOOL)flag
  63. {
  64. looksForHTMLLinkFiles = YES;
  65. }
  66. - (id)nextObject
  67. {
  68. NSString *s;
  69. while ((s = [e nextObject]) != nil)
  70. {
  71. BOOL found = NO;
  72. NSString *extension = [s pathExtension];
  73. if (looksForHTMLLinkFiles)
  74. {
  75. if ([extension isEqualToString: @"htmlink"])
  76. {
  77. found = YES;
  78. }
  79. }
  80. else if ([extension isEqualToString: @"html"]
  81. || [extension isEqualToString: @"HTML"]
  82. || [extension isEqualToString: @"htm"]
  83. || [extension isEqualToString: @"HTM"])
  84. {
  85. found = YES;
  86. }
  87. if ([[[e fileAttributes] fileType] isEqual: NSFileTypeDirectory]
  88. && verbose)
  89. {
  90. GSPrintf(stdout, @" traversing %@\n", s);
  91. }
  92. if (found)
  93. {
  94. if (returnsAbsolutePaths)
  95. {
  96. /* NSDirectoryEnumerator returns the relative path, we
  97. return the absolute. */
  98. return [basePath stringByAppendingPathComponent: s];
  99. }
  100. else
  101. {
  102. return s;
  103. }
  104. }
  105. }
  106. return nil;
  107. }
  108. @end
  109. /* The HTMLLinker class is very simple and is the core of the linker.
  110. It just keeps a relocation, and is able to fixup a link by using
  111. the relocation table. */
  112. @interface HTMLLinker : NSObject
  113. {
  114. BOOL warn;
  115. BOOL hasPathMappings;
  116. NSMutableDictionary *pathMappings;
  117. NSMutableDictionary *relocationTable;
  118. }
  119. - (id)initWithWarnFlag: (BOOL)v;
  120. - (void)registerRelocationFile: (NSString *)pathOnDisk;
  121. - (void)registerDestinationFile: (NSString *)pathOnDisk;
  122. /* Register a new path mapping. */
  123. - (void)registerPathMappings: (NSDictionary *)dict;
  124. /* Resolve the link 'link' by fixing it up using the relocation table.
  125. Return the resolved link. 'logFile' is only used to print error
  126. messages. It is the file in which the link is originally found; if
  127. there is problem resolving the link, the warning message printed
  128. out states that the problem is in file 'logFile'. */
  129. - (NSString *)resolveLink: (NSString *)link
  130. logFile: (NSString *)logFile;
  131. @end
  132. /* All the parsing code is in the following class. It's not a real
  133. parser in the sense that it is just performing its minimal duty in
  134. the quickest possible way, so calling this a parser is a bit of a
  135. exaggeration ... this code can run very quickly through an HTML
  136. string, extracting the <a name="yyy"> tags or fixing up the <a
  137. href="xxx" rel="dynamical"> tags. No more HTML parsing than this
  138. is done. Remarkably, this does not need XML support in the base
  139. library, so you can use the HTML linker on any system. This class
  140. was written in order to perform its trivial, mechanical duty /very
  141. fast/. You want to be able to run the linker often and on a lot of
  142. files and still be happy. FIXME - Need to implement support for
  143. newer HTML where you can use id="name" in any tag. */
  144. @interface HTMLParser : NSObject
  145. {
  146. /* The HTML code that we work on. */
  147. unichar *chars;
  148. unsigned length;
  149. }
  150. /* Init with some HTML code to parse. */
  151. - (id)initWithCode: (NSString *)HTML;
  152. /* Extract all the <a name="xxx"> tags from the HTML code, and return
  153. a list of them. */
  154. - (NSArray *)names;
  155. /* Fix up all the links in the HTML code by feeding each of them to
  156. the provided HTMLLinker; return the fixed up HTML code. If
  157. linksMarker is nil, attempts to fix up all links in the HTML code;
  158. if it is not-nil, only attempt to fixup links with rel=marker.
  159. logFile is the file we are fixing up; it's only used when a warning
  160. is issued because there is problem in the linking - the warning
  161. message is displayed as being about links in the file logFile. */
  162. - (NSString *)resolveLinksUsingHTMLLinker: (HTMLLinker *)linker
  163. logFile: (NSString *)logFile
  164. linksMarker: (NSString *)marker;
  165. @end
  166. @implementation HTMLParser
  167. - (id)initWithCode: (NSString *)HTML
  168. {
  169. length = [HTML length];
  170. chars = malloc (sizeof(unichar) * length);
  171. [HTML getCharacters: chars];
  172. return [super init];
  173. }
  174. - (void)dealloc
  175. {
  176. free (chars);
  177. [super dealloc];
  178. }
  179. - (NSArray *)names
  180. {
  181. NSMutableArray *names = AUTORELEASE ([NSMutableArray new]);
  182. unsigned i = 0;
  183. while (i + 3 < length)
  184. {
  185. /* We ignore anything except stuff which begins with "<a ". */
  186. if ((chars[i] == '<')
  187. && (chars[i + 1] == 'A' || chars[i + 1] == 'a')
  188. && (chars[i + 2] == ' '))
  189. {
  190. /* Ok - we got the '<a ' tag, now parse it ... we're
  191. searching for a name attribute. */
  192. NSString *name = nil;
  193. i += 3;
  194. while (1)
  195. {
  196. /* A marker for the start of strings. */
  197. unsigned s;
  198. /* If this is not a 'name' attribute, setting this to YES
  199. cause us to ignore it and go on to the next one. */
  200. BOOL isNameAttribute = NO;
  201. /* Read in an attribute, of the form xxx="yyy" or
  202. xxx=yyy or similar, and save it if it is a name
  203. attribute. */
  204. /* Skip spaces. */
  205. while (i < length && (chars[i] == ' '
  206. || chars[i] == '\n'
  207. || chars[i] == '\r'
  208. || chars[i] == '\t'))
  209. { i++; }
  210. if (i == length) { break; }
  211. /* Read the attribute. */
  212. s = i;
  213. while (i < length && (chars[i] != ' '
  214. && chars[i] != '\n'
  215. && chars[i] != '\r'
  216. && chars[i] != '\t'
  217. && chars[i] != '='
  218. && chars[i] != '>'))
  219. { i++; }
  220. if (i == length) { break; }
  221. if (chars[i] == '>') { break; }
  222. /* I suppose i == s might happen if the file contains <a
  223. ="nicola"> */
  224. if (i != s)
  225. {
  226. /* If name != nil we already found it so don't bother. */
  227. if (name == nil)
  228. {
  229. NSString *attribute;
  230. attribute = [NSString stringWithCharacters: &chars[s]
  231. length: (i - s)];
  232. /* Lowercase name so that eg, HREF and href are the
  233. same. */
  234. attribute = [attribute lowercaseString];
  235. if ([attribute isEqualToString: @"name"])
  236. {
  237. isNameAttribute = YES;
  238. }
  239. }
  240. }
  241. /* Skip spaces. */
  242. while (i < length && (chars[i] == ' '
  243. || chars[i] == '\n'
  244. || chars[i] == '\r'
  245. || chars[i] == '\t'))
  246. { i++; }
  247. if (i == length) { break; }
  248. /* Read the '=' */
  249. if (chars[i] == '=')
  250. {
  251. i++;
  252. }
  253. else
  254. {
  255. /* No '=' -- go on with the next attribute. */
  256. continue;
  257. }
  258. if (i == length) { break; }
  259. /* Skip spaces. */
  260. while (i < length && (chars[i] == ' '
  261. || chars[i] == '\n'
  262. || chars[i] == '\r'
  263. || chars[i] == '\t'))
  264. { i++; }
  265. if (i == length) { break; }
  266. /* Read the value. */
  267. if (chars[i] == '"')
  268. {
  269. /* Skip the '"', then read up to a '"'. */
  270. i++;
  271. if (i == length) { break; }
  272. s = i;
  273. while (i < length && (chars[i] != '"'))
  274. { i++; }
  275. }
  276. else if (chars[i] == '\'')
  277. {
  278. /* Skip the '\'', then read up to a '\''. */
  279. i++;
  280. if (i == length) { break; }
  281. s = i;
  282. while (i < length && (chars[i] != '\''))
  283. { i++; }
  284. }
  285. else
  286. {
  287. /* Read up to a space or '>'. */
  288. s = i;
  289. while (i < length
  290. && (chars[i] != ' '
  291. && chars[i] != '\n'
  292. && chars[i] != '\r'
  293. && chars[i] != '\t'
  294. && chars[i] != '>'))
  295. { i++; }
  296. }
  297. if (name == nil && isNameAttribute)
  298. {
  299. if (i == s)
  300. {
  301. /* I suppose this might happen if the file
  302. contains <a name=> */
  303. name = @"";
  304. }
  305. else
  306. {
  307. name = [NSString stringWithCharacters: &chars[s]
  308. length: (i - s)];
  309. }
  310. }
  311. }
  312. if (name != nil)
  313. {
  314. [names addObject: name];
  315. }
  316. }
  317. i++;
  318. }
  319. return names;
  320. }
  321. - (NSString *)resolveLinksUsingHTMLLinker: (HTMLLinker *)linker
  322. logFile: (NSString *)logFile
  323. linksMarker: (NSString *)marker
  324. {
  325. /* We represent the output as a linked list. Each element in the
  326. linked list represents a string; concatenating all the strings in
  327. the linked list, you obtain the output. The trick is that these
  328. strings in the linked list might actually be pointers inside the
  329. chars array ... we are never copying stuff from the chars array -
  330. just keeping pointers to substrings inside it - till we generate
  331. the final string at the end ... for speed and efficiency reasons
  332. of course. */
  333. struct stringFragment
  334. {
  335. unichar *chars;
  336. unsigned length;
  337. BOOL needsFreeing;
  338. struct stringFragment *next;
  339. } *head, *tail;
  340. /* The index of the beginning of the last string fragment (the tail). */
  341. unsigned tailIndex = 0;
  342. /* The temporary index. */
  343. unsigned i = 0;
  344. /* The total number of chars in the output string. We don't know
  345. this beforehand because each time we fix up a link, we might add
  346. or remove characters from the output. We update
  347. totalNumberOfChars each time we close a stringFragment. */
  348. unsigned totalNumberOfChars = 0;
  349. /* Initialize the linked list. */
  350. head = malloc (sizeof (struct stringFragment));
  351. head->chars = chars;
  352. head->length = 0;
  353. head->needsFreeing = NO;
  354. head->next = NULL;
  355. /* The last string fragment is the first one at the beginning. */
  356. tail = head;
  357. while (i + 3 < length)
  358. {
  359. /* We ignore anything except stuff which begins with "<a ". */
  360. if ((chars[i] == '<')
  361. && (chars[i + 1] == 'A' || chars[i + 1] == 'a')
  362. && (chars[i + 2] == ' '))
  363. {
  364. /* Ok - we got the '<a ' tag, now parse it ... we're
  365. searching for a href and a rel attributes. */
  366. NSString *href = nil;
  367. NSString *rel = nil;
  368. /* We also need to keep track of where the href starts and
  369. where it ends, because we are going to replace it with a
  370. different one (the fixed up one) later on if we determine
  371. we should do it. */
  372. unsigned hrefStart = 0, hrefEnd = 0;
  373. i += 3;
  374. while (1)
  375. {
  376. /* A marker for the start of strings. */
  377. unsigned s;
  378. /* If this is an interesting (href/rel) attribute or
  379. not, and which one. */
  380. BOOL isHrefAttribute = NO;
  381. BOOL isRelAttribute = NO;
  382. /* Read in an attribute, of the form xxx="yyy" or
  383. xxx=yyy or similar, and save it if it is a name
  384. attribute. */
  385. /* Skip spaces. */
  386. while (i < length && (chars[i] == ' '
  387. || chars[i] == '\n'
  388. || chars[i] == '\r'
  389. || chars[i] == '\t'))
  390. { i++; }
  391. if (i == length) { break; }
  392. /* Read the attribute. */
  393. s = i;
  394. while (i < length && (chars[i] != ' '
  395. && chars[i] != '\n'
  396. && chars[i] != '\r'
  397. && chars[i] != '\t'
  398. && chars[i] != '='
  399. && chars[i] != '>'))
  400. { i++; }
  401. if (i == length) { break; }
  402. if (chars[i] == '>') { break; }
  403. /* I suppose i == s might happen if the file contains <a
  404. ="nicola"> */
  405. if (i != s)
  406. {
  407. /* If href != nil && rel != nil we already found it
  408. so don't bother. */
  409. if (href == nil || rel == nil)
  410. {
  411. NSString *attribute;
  412. attribute = [NSString stringWithCharacters: &chars[s]
  413. length: (i - s)];
  414. /* Lowercase name so that eg, HREF and href are the
  415. same. */
  416. attribute = [attribute lowercaseString];
  417. if (href == nil
  418. && [attribute isEqualToString: @"href"])
  419. {
  420. isHrefAttribute = YES;
  421. }
  422. else if (rel == nil
  423. && [attribute isEqualToString: @"rel"])
  424. {
  425. isRelAttribute = YES;
  426. }
  427. }
  428. }
  429. /* Skip spaces. */
  430. while (i < length && (chars[i] == ' '
  431. || chars[i] == '\n'
  432. || chars[i] == '\r'
  433. || chars[i] == '\t'))
  434. { i++; }
  435. if (i == length) { break; }
  436. /* Read the '=' */
  437. if (chars[i] == '=')
  438. {
  439. i++;
  440. }
  441. else
  442. {
  443. /* No '=' -- go on with the next attribute. */
  444. continue;
  445. }
  446. if (i == length) { break; }
  447. /* Skip spaces. */
  448. while (i < length && (chars[i] == ' '
  449. || chars[i] == '\n'
  450. || chars[i] == '\r'
  451. || chars[i] == '\t'))
  452. { i++; }
  453. if (i == length) { break; }
  454. /* Read the value. */
  455. if (isHrefAttribute)
  456. {
  457. /* Remeber that href starts here. */
  458. hrefStart = i;
  459. }
  460. if (chars[i] == '"')
  461. {
  462. /* Skip the '"', then read up to a '"'. */
  463. i++;
  464. if (i == length) { break; }
  465. s = i;
  466. while (i < length && (chars[i] != '"'))
  467. { i++; }
  468. if (isHrefAttribute)
  469. {
  470. /* Remeber that href ends here. We don't want
  471. the ending " because we already insert those
  472. by our own. */
  473. hrefEnd = i + 1;
  474. }
  475. }
  476. else if (chars[i] == '\'')
  477. {
  478. /* Skip the '\'', then read up to a '\''. */
  479. i++;
  480. if (i == length) { break; }
  481. s = i;
  482. while (i < length && (chars[i] != '\''))
  483. { i++; }
  484. if (isHrefAttribute)
  485. {
  486. hrefEnd = i + 1;
  487. }
  488. }
  489. else
  490. {
  491. /* Read up to a space or '>'. */
  492. s = i;
  493. while (i < length
  494. && (chars[i] != ' '
  495. && chars[i] != '\n'
  496. && chars[i] != '\r'
  497. && chars[i] != '\t'
  498. && chars[i] != '>'))
  499. { i++; }
  500. if (isHrefAttribute)
  501. {
  502. /* We do want the ending space. */
  503. hrefEnd = i;
  504. }
  505. }
  506. if (i == length)
  507. {
  508. break;
  509. }
  510. if (hrefEnd >= length)
  511. {
  512. hrefEnd = length - 1;
  513. }
  514. if (isRelAttribute)
  515. {
  516. if (i == s)
  517. {
  518. /* I suppose this might happen if the file
  519. contains <a rel=> */
  520. rel = @"";
  521. }
  522. else
  523. {
  524. rel = [NSString stringWithCharacters: &chars[s]
  525. length: (i - s)];
  526. }
  527. }
  528. if (isHrefAttribute)
  529. {
  530. if (i == s)
  531. {
  532. /* I suppose this might happen if the file
  533. contains <a href=> */
  534. href = @"";
  535. }
  536. else
  537. {
  538. href = [NSString stringWithCharacters: &chars[s]
  539. length: (i - s)];
  540. }
  541. }
  542. }
  543. if (href != nil && ((marker == nil)
  544. || [rel isEqualToString: marker]))
  545. {
  546. /* Ok - fixup the link. */
  547. NSString *link;
  548. struct stringFragment *s;
  549. link = [linker resolveLink: href logFile: logFile];
  550. /* Add " before and after the link. */
  551. link = [NSString stringWithFormat: @"\"%@\"", link];
  552. /* Close the previous string fragment at hrefStart. */
  553. tail->length = hrefStart - tailIndex;
  554. totalNumberOfChars += tail->length;
  555. /* Insert immediately afterwards a string fragment containing
  556. the fixed up link. */
  557. s = malloc (sizeof (struct stringFragment));
  558. s->length = [link length];
  559. s->chars = malloc (sizeof(unichar) * s->length);
  560. [link getCharacters: s->chars];
  561. s->needsFreeing = YES;
  562. s->next = NULL;
  563. tail->next = s;
  564. tail = s;
  565. totalNumberOfChars += tail->length;
  566. /* Now prepare the new tail to start just after the end
  567. of the original href in the original HTML code. */
  568. s = malloc (sizeof (struct stringFragment));
  569. s->length = 0;
  570. s->chars = &chars[hrefEnd];
  571. s->needsFreeing = NO;
  572. s->next = NULL;
  573. tail->next = s;
  574. tail = s;
  575. tailIndex = hrefEnd;
  576. }
  577. }
  578. i++;
  579. }
  580. /* Close the last open string fragment. */
  581. tail->length = length - tailIndex;
  582. totalNumberOfChars += tail->length;
  583. /* Generate the output. */
  584. {
  585. NSString *result;
  586. /* Allocate space for the whole output in a single chunk now that
  587. we know how big it should be. */
  588. unichar *outputChars = malloc (sizeof(unichar) * totalNumberOfChars);
  589. unsigned j = 0;
  590. /* Copy into the output all the string fragments, destroying each
  591. of them as we go on. */
  592. while (head != NULL)
  593. {
  594. struct stringFragment *s;
  595. memcpy (&outputChars[j], head->chars,
  596. sizeof(unichar) * head->length);
  597. j += head->length;
  598. if (head->needsFreeing)
  599. {
  600. free (head->chars);
  601. }
  602. s = head->next;
  603. free (head);
  604. head = s;
  605. }
  606. result = [NSString stringWithCharacters: outputChars
  607. length: totalNumberOfChars];
  608. free(outputChars);
  609. return result;
  610. }
  611. }
  612. @end
  613. @implementation HTMLLinker
  614. - (id)initWithWarnFlag: (BOOL)v
  615. {
  616. warn = v;
  617. relocationTable = [NSMutableDictionary new];
  618. pathMappings = [NSMutableDictionary new];
  619. return [super init];
  620. }
  621. - (void)dealloc
  622. {
  623. RELEASE (relocationTable);
  624. RELEASE (pathMappings);
  625. [super dealloc];
  626. }
  627. - (void)registerRelocationFile: (NSString *)pathOnDisk
  628. {
  629. /* We only accept absolute paths. */
  630. if (![pathOnDisk isAbsolutePath])
  631. {
  632. pathOnDisk = [currentPath stringByAppendingPathComponent: pathOnDisk];
  633. }
  634. /* Check if it's a directory; if it is, enumerate all .htmlink files
  635. inside it, and add all of them. */
  636. {
  637. BOOL isDir;
  638. if (![fileManager fileExistsAtPath: pathOnDisk isDirectory: &isDir])
  639. {
  640. NSLog (@"Warning - relocation file '%@' not found - ignored",
  641. pathOnDisk);
  642. return;
  643. }
  644. else
  645. {
  646. if (isDir)
  647. {
  648. HTMLDirectoryEnumerator *e;
  649. NSString *filename;
  650. e = [HTMLDirectoryEnumerator alloc];
  651. e = [[e initWithBasePath: pathOnDisk] autorelease];
  652. [e setLooksForHTMLLinkFiles: YES];
  653. [e setReturnsAbsolutePaths: YES];
  654. while ((filename = [e nextObject]) != nil)
  655. {
  656. [self registerRelocationFile: filename];
  657. }
  658. return;
  659. }
  660. }
  661. }
  662. /* Now, read the mappings in the file. */
  663. {
  664. NSString *file = [NSString stringWithContentsOfFile: pathOnDisk];
  665. NSString *path = [pathOnDisk stringByDeletingLastPathComponent];
  666. NSDictionary *d = [file propertyList];
  667. NSEnumerator *e = [d keyEnumerator];
  668. NSString *name;
  669. while ((name = [e nextObject]) != nil)
  670. {
  671. NSString *v = [d objectForKey: name];
  672. NSString *filePath;
  673. filePath = [path stringByAppendingPathComponent: v];
  674. if (hasPathMappings)
  675. {
  676. /* Manage pathMappings: try to match any of the
  677. pathMappings against pathOnDisk, and perform the path
  678. mapping if we can match. */
  679. NSEnumerator *en = [pathMappings keyEnumerator];
  680. NSString *key;
  681. while ((key = [en nextObject]))
  682. {
  683. if ([filePath hasPrefix: key])
  684. {
  685. NSString *value = [pathMappings objectForKey: key];
  686. filePath = [filePath substringFromIndex: [key length]];
  687. filePath = [value stringByAppendingPathComponent:
  688. filePath];
  689. break;
  690. }
  691. }
  692. }
  693. [relocationTable setObject: filePath forKey: name];
  694. }
  695. }
  696. }
  697. - (void)registerDestinationFile: (NSString *)pathOnDisk
  698. {
  699. NSString *fullPath = pathOnDisk;
  700. /* We only accept absolute paths. */
  701. if (![pathOnDisk isAbsolutePath])
  702. {
  703. pathOnDisk = [currentPath stringByAppendingPathComponent: pathOnDisk];
  704. }
  705. /* Check if it's a directory; if it is, enumerate all HTML files
  706. inside it, and add all of them. */
  707. {
  708. BOOL isDir;
  709. if (![fileManager fileExistsAtPath: pathOnDisk isDirectory: &isDir])
  710. {
  711. NSLog (@"Warning - destination file '%@' not found - ignored",
  712. pathOnDisk);
  713. return;
  714. }
  715. else
  716. {
  717. if (isDir)
  718. {
  719. HTMLDirectoryEnumerator *e;
  720. NSString *filename;
  721. e = [HTMLDirectoryEnumerator alloc];
  722. e = [[e initWithBasePath: pathOnDisk] autorelease];
  723. [e setReturnsAbsolutePaths: YES];
  724. while ((filename = [e nextObject]) != nil)
  725. {
  726. [self registerDestinationFile: filename];
  727. }
  728. return;
  729. }
  730. }
  731. }
  732. if (hasPathMappings)
  733. {
  734. /* Manage pathMappings: try to match any of the pathMappings
  735. against pathOnDisk, and perform the path mapping if we can
  736. match. */
  737. NSEnumerator *e = [pathMappings keyEnumerator];
  738. NSString *key;
  739. while ((key = [e nextObject]))
  740. {
  741. if ([pathOnDisk hasPrefix: key])
  742. {
  743. NSString *value = [pathMappings objectForKey: key];
  744. fullPath = [pathOnDisk substringFromIndex: [key length]];
  745. fullPath = [value stringByAppendingPathComponent: fullPath];
  746. break;
  747. }
  748. }
  749. }
  750. /* Now, read all the names from the file. */
  751. {
  752. NSString *file = [NSString stringWithContentsOfFile: pathOnDisk];
  753. HTMLParser *p = [[HTMLParser alloc] initWithCode: file];
  754. NSArray *names = [p names];
  755. unsigned i, count;
  756. RELEASE (p);
  757. count = [names count];
  758. for (i = 0; i < count; i++)
  759. {
  760. NSString *name = [names objectAtIndex: i];
  761. [relocationTable setObject: fullPath forKey: name];
  762. }
  763. }
  764. }
  765. - (void)registerPathMappings: (NSDictionary *)dict
  766. {
  767. NSEnumerator *e = [dict keyEnumerator];
  768. NSString *key;
  769. while ((key = [e nextObject]))
  770. {
  771. NSString *value = [dict objectForKey: key];
  772. [pathMappings setObject: value forKey: key];
  773. }
  774. hasPathMappings = YES;
  775. }
  776. - (NSString *)resolveLink: (NSString *)link
  777. logFile: (NSString *)logFile
  778. {
  779. NSString *fileLink;
  780. NSString *nameLink;
  781. NSString *relocatedFileLink;
  782. NSString *file;
  783. /* Do nothing if this is evidently *not* a dynamical link to fixup. */
  784. if ([link hasPrefix: @"mailto:"] || [link hasPrefix: @"news:"])
  785. {
  786. return link;
  787. }
  788. {
  789. /* Break the link string into fileLink (everything which is before
  790. the `#'), and nameLink (everything which is after the `#', `#'
  791. not included). For example, if link is
  792. 'NSObject_Class.html#isa', then fileLink is
  793. 'NSObject_Class.html' and nameLink is 'isa'. */
  794. /* Look for the #. */
  795. NSRange hashRange = [link rangeOfString: @"#"];
  796. if (hashRange.location == NSNotFound)
  797. {
  798. fileLink = link;
  799. nameLink = nil;
  800. }
  801. else
  802. {
  803. fileLink = [link substringToIndex: hashRange.location];
  804. if (hashRange.location + 1 < [link length])
  805. {
  806. nameLink = [link substringFromIndex: (hashRange.location + 1)];
  807. }
  808. else
  809. {
  810. nameLink = nil;
  811. }
  812. }
  813. }
  814. /* Now lookup nameLink. */
  815. /* If it's "", it is not something we can fixup. */
  816. if (nameLink == nil || [nameLink isEqualToString: @""])
  817. {
  818. relocatedFileLink = fileLink;
  819. }
  820. else
  821. {
  822. /* Now simply look it up in our relocation table. */
  823. file = [relocationTable objectForKey: nameLink];
  824. /* Not found - leave it unfixed. */
  825. if (file == nil)
  826. {
  827. if (warn && [fileLink length] > 0)
  828. {
  829. GSPrintf(stderr, @"%@: Unresolved reference to '%@'\n",
  830. logFile, nameLink);
  831. }
  832. relocatedFileLink = fileLink;
  833. }
  834. else
  835. {
  836. relocatedFileLink = file;
  837. }
  838. }
  839. /* Now build up the final relocated link, and return it. */
  840. if (nameLink != nil)
  841. {
  842. return [NSString stringWithFormat: @"%@#%@", relocatedFileLink,
  843. nameLink];
  844. }
  845. else
  846. {
  847. return relocatedFileLink;
  848. }
  849. }
  850. @end
  851. static NSDictionary *
  852. build_relocation_table_for_directory (NSString *dir)
  853. {
  854. BOOL isDir;
  855. if (verbose)
  856. GSPrintf(stdout, @" Building relcation table for %@\n", dir);
  857. /* Check if it's a directory; if it is, enumerate all HTML files
  858. inside it, and add all of them. */
  859. if (![fileManager fileExistsAtPath: dir isDirectory: &isDir])
  860. {
  861. NSLog (@"%@ does not exist - exiting", dir);
  862. exit (1);
  863. }
  864. else if (!isDir)
  865. {
  866. NSLog (@"%@ is not a directory - exiting", dir);
  867. exit (1);
  868. }
  869. else
  870. {
  871. HTMLDirectoryEnumerator *e;
  872. NSString *filename;
  873. NSMutableDictionary *relocationTable;
  874. relocationTable = [NSMutableDictionary new];
  875. IF_NO_GC ([relocationTable autorelease];)
  876. e = [HTMLDirectoryEnumerator alloc];
  877. e = [[e initWithBasePath: dir] autorelease];
  878. /* The relocation table for a directory is relative to the
  879. directory top, so that the whole directory can be moved
  880. around without having to regenerate the .htmlink file. */
  881. [e setReturnsAbsolutePaths: NO];
  882. while ((filename = [e nextObject]) != nil)
  883. {
  884. NSString *fullPath;
  885. NSString *file;
  886. HTMLParser *p;
  887. NSArray *names;
  888. unsigned i, count;
  889. fullPath = [dir stringByAppendingPathComponent: filename];
  890. file = [NSString stringWithContentsOfFile: fullPath];
  891. p = [[HTMLParser alloc] initWithCode: file];
  892. names = [p names];
  893. RELEASE (p);
  894. count = [names count];
  895. for (i = 0; i < count; i++)
  896. {
  897. NSString *name = [names objectAtIndex: i];
  898. [relocationTable setObject: filename forKey: name];
  899. }
  900. }
  901. return relocationTable;
  902. }
  903. }
  904. static void print_help_and_exit ()
  905. {
  906. printf ("GNUstep HTMLLinker\n");
  907. printf ("Usage: HTMLLinker [options] input_files [-l relocation_file] [-d destination_file]\n");
  908. printf ("Multiple input files, and multiple -l and -d options are allowed.\n");
  909. printf (" `options' include:\n");
  910. printf (" --help: print this message;\n");
  911. printf (" --version: print version information;\n");
  912. printf (" --verbose: print information while processing;\n");
  913. printf (" -Warn NO: do not print warnings about unresolved links;\n");
  914. printf (" -LinksMarker xxx: only fixup links with attribute rel=xxx;\n");
  915. printf (" -FixupAllLinks YES: attempt to fixup all links (not only ones with the marker);\n");
  916. printf (" -PathMappingsFile file: read path mappings from file (a dictionary);\n");
  917. printf (" -PathMappings '{\"/usr/doc\"=\"/Doc\";}': use the supplied path mappings;\n");
  918. printf (" -BuildRelocationFileForDir yyy: build a relocation file for the dir yyy\n");
  919. printf (" and save it into yyy/table.htmlink. This option is special\n");
  920. printf (" and prevents any other processing by the linker.\n");
  921. exit (0);
  922. }
  923. static void print_version_and_exit ()
  924. {
  925. printf ("GNUstep HTMLLinker (gnustep-base version %d.%d.%d)\n",
  926. GNUSTEP_BASE_MAJOR_VERSION,
  927. GNUSTEP_BASE_MINOR_VERSION,
  928. GNUSTEP_BASE_SUBMINOR_VERSION);
  929. exit (0);
  930. }
  931. int main (int argc, char** argv, char** env)
  932. {
  933. NSUserDefaults *userDefs;
  934. NSArray *args;
  935. NSMutableArray *inputFiles;
  936. unsigned i, count;
  937. BOOL warn;
  938. NSString *linksMarker;
  939. HTMLLinker *linker;
  940. CREATE_AUTORELEASE_POOL(pool);
  941. #ifdef GS_PASS_ARGUMENTS
  942. GSInitializeProcess(argc, argv, env);
  943. #endif
  944. /* Set up the cache. */
  945. fileManager = [NSFileManager defaultManager];
  946. currentPath = [fileManager currentDirectoryPath];
  947. /* Read basic defaults. */
  948. userDefs = [NSUserDefaults standardUserDefaults];
  949. /* defaults are -
  950. -Warn YES
  951. -LinksMarker dynamic
  952. -FixupAllLinks NO
  953. */
  954. [userDefs registerDefaults: [NSDictionary dictionaryWithObjectsAndKeys:
  955. @"dynamic", @"LinksMarker",
  956. @"YES", @"Warn",
  957. nil]];
  958. warn = [userDefs boolForKey: @"Warn"];
  959. linksMarker = [userDefs stringForKey: @"LinksMarker"];
  960. /* If -BuildRelocationFileForDir xxx is passed on the command line,
  961. build a relocation file for the directory xxx and save it in
  962. xxx/table.htmlink. */
  963. {
  964. NSString *relFile;
  965. relFile = [userDefs stringForKey: @"BuildRelocationFileForDir"];
  966. if (relFile != nil)
  967. {
  968. NSDictionary *table;
  969. NSString *outputFile;
  970. outputFile = [relFile stringByAppendingPathComponent:
  971. @"table.htmlink"];
  972. table = build_relocation_table_for_directory (relFile);
  973. [table writeToFile: outputFile atomically: YES];
  974. exit (0);
  975. }
  976. }
  977. /* Create the linker object. */
  978. linker = [[HTMLLinker alloc] initWithWarnFlag: warn];
  979. /* First, read all path mappings (before reading any destination
  980. file / relocation file, so we can relocate properly. */
  981. /* Read path mappings from PathMappingsFile if specified. */
  982. {
  983. NSString *pathMapFile = [userDefs stringForKey: @"PathMappingsFile"];
  984. if (pathMapFile != nil)
  985. {
  986. NSDictionary *mappings;
  987. mappings = [NSDictionary dictionaryWithContentsOfFile: pathMapFile];
  988. if (mappings == nil)
  989. {
  990. NSLog (@"Warning - %@ does not contain a dictionary - ignored",
  991. pathMapFile);
  992. }
  993. else
  994. {
  995. [linker registerPathMappings: mappings];
  996. }
  997. }
  998. }
  999. /* Add PathMappings specified on the command line if any. */
  1000. {
  1001. NSDictionary *paths = [userDefs dictionaryForKey: @"PathMappings"];
  1002. if (paths != nil)
  1003. {
  1004. [linker registerPathMappings: paths];
  1005. }
  1006. }
  1007. /* All non-options on the command line are:
  1008. input files
  1009. destination files if they come after a -d
  1010. relocation files if they come after a -l
  1011. Directories as input files or destination files means 'all .html, .htm,
  1012. .HTML, .HTM files in the directory and subdirectories'.
  1013. */
  1014. args = [[NSProcessInfo processInfo] arguments];
  1015. count = [args count];
  1016. inputFiles = AUTORELEASE ([NSMutableArray new]);
  1017. for (i = 1; i < count; i++)
  1018. {
  1019. NSString *arg = [args objectAtIndex: i];
  1020. if ([arg characterAtIndex: 0] == '-')
  1021. {
  1022. NSString *opt;
  1023. opt = ([arg characterAtIndex: 1] == '-') ?
  1024. [arg substringFromIndex: 2] : [arg substringFromIndex: 1];
  1025. if ([opt isEqualToString: @"help"]
  1026. || [opt isEqualToString: @"h"])
  1027. {
  1028. print_help_and_exit ();
  1029. }
  1030. else if ([opt isEqualToString: @"version"]
  1031. || [opt isEqualToString: @"V"])
  1032. {
  1033. print_version_and_exit ();
  1034. }
  1035. else if ([opt isEqualToString: @"verbose"]
  1036. || [opt isEqualToString: @"v"])
  1037. {
  1038. verbose++;
  1039. }
  1040. else if ([opt isEqualToString: @"d"])
  1041. {
  1042. if ((i + 1) < count)
  1043. {
  1044. i++;
  1045. /* Register a destination file. */
  1046. [linker registerDestinationFile: [args objectAtIndex: i]];
  1047. }
  1048. else
  1049. {
  1050. NSLog (@"Missing argument to -d");
  1051. }
  1052. }
  1053. else if ([opt isEqualToString: @"l"])
  1054. {
  1055. if ((i + 1) < count)
  1056. {
  1057. i++;
  1058. /* Register a destination file. */
  1059. [linker registerRelocationFile: [args objectAtIndex: i]];
  1060. }
  1061. else
  1062. {
  1063. NSLog (@"Missing argument to -l");
  1064. }
  1065. }
  1066. else
  1067. {
  1068. /* A GNUstep default - skip it and the next argument. */
  1069. if ((i + 1) < count)
  1070. {
  1071. i++;
  1072. continue;
  1073. }
  1074. }
  1075. }
  1076. else
  1077. {
  1078. BOOL isDir;
  1079. if (![fileManager fileExistsAtPath: arg isDirectory: &isDir])
  1080. {
  1081. NSLog (@"Warning - input file '%@' not found - ignored", arg);
  1082. }
  1083. else
  1084. {
  1085. if (isDir)
  1086. {
  1087. HTMLDirectoryEnumerator *e;
  1088. NSString *filename;
  1089. e = [[[HTMLDirectoryEnumerator alloc]
  1090. initWithBasePath: arg] autorelease];
  1091. [e setReturnsAbsolutePaths: YES];
  1092. while ((filename = [e nextObject]) != nil)
  1093. {
  1094. [inputFiles addObject: filename];
  1095. }
  1096. }
  1097. else
  1098. {
  1099. [inputFiles addObject: arg];
  1100. }
  1101. }
  1102. }
  1103. }
  1104. count = [inputFiles count];
  1105. if (count == 0)
  1106. {
  1107. NSLog (@"No input files specified.");
  1108. }
  1109. for (i = 0; i < count; i++)
  1110. {
  1111. NSString *inputFile;
  1112. NSString *inputFileContents;
  1113. HTMLParser *parser;
  1114. inputFile = [inputFiles objectAtIndex: i];
  1115. if (verbose)
  1116. GSPrintf(stdout, @" %@\n", inputFile);
  1117. inputFileContents = [NSString stringWithContentsOfFile: inputFile];
  1118. parser = [[HTMLParser alloc] initWithCode: inputFileContents];
  1119. inputFileContents = [parser resolveLinksUsingHTMLLinker: linker
  1120. logFile: inputFile
  1121. linksMarker: linksMarker];
  1122. [inputFileContents writeToFile: inputFile atomically: YES];
  1123. RELEASE (parser);
  1124. }
  1125. RELEASE (linker);
  1126. [pool drain];
  1127. return 0;
  1128. }