OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2009, Google Inc. | 2 * Copyright 2009, Google Inc. |
3 * All rights reserved. | 3 * All rights reserved. |
4 * | 4 * |
5 * Redistribution and use in source and binary forms, with or without | 5 * Redistribution and use in source and binary forms, with or without |
6 * modification, are permitted provided that the following conditions are | 6 * modification, are permitted provided that the following conditions are |
7 * met: | 7 * met: |
8 * | 8 * |
9 * * Redistributions of source code must retain the above copyright | 9 * * Redistributions of source code must retain the above copyright |
10 * notice, this list of conditions and the following disclaimer. | 10 * notice, this list of conditions and the following disclaimer. |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
42 'Number': true, | 42 'Number': true, |
43 'object': true, | 43 'object': true, |
44 'Object': true, | 44 'Object': true, |
45 '*': true, | 45 '*': true, |
46 '...': true, | 46 '...': true, |
47 'string': true, | 47 'string': true, |
48 'String': true, | 48 'String': true, |
49 'void': true, | 49 'void': true, |
50 'undefined': true}; | 50 'undefined': true}; |
51 var g_unknownTypes = { }; | 51 var g_unknownTypes = { }; |
| 52 var g_numErrors = 0; |
52 | 53 |
53 /** | 54 /** |
54 * Called automatically by JsDoc Toolkit. | 55 * Called automatically by JsDoc Toolkit. |
55 * @param {SymbolSet} symbolSet Set of all symbols in all files. | 56 * @param {SymbolSet} symbolSet Set of all symbols in all files. |
56 */ | 57 */ |
57 function publish(symbolSet) { | 58 function publish(symbolSet) { |
58 publish.conf = { // trailing slash expected for dirs | 59 publish.conf = { // trailing slash expected for dirs |
59 ext: '.ezt', | 60 ext: '.ezt', |
60 outDir: JSDOC.opt.d || SYS.pwd + '../out/jsdoc/', | 61 outDir: JSDOC.opt.d || SYS.pwd + '../out/jsdoc/', |
61 templatesDir: JSDOC.opt.t || SYS.pwd + '../templates/jsdoc/', | 62 templatesDir: JSDOC.opt.t || SYS.pwd + '../templates/jsdoc/', |
62 symbolsDir: '', | 63 symbolsDir: '', |
63 prefix: 'js_1_0_'}; | 64 prefix: JSDOC.opt.D.prefix || 'js_1_0_'}; |
64 publish.conf.srcDir = publish.conf.outDir + 'src/' | 65 publish.conf.srcDir = publish.conf.outDir + 'src/' |
65 publish.conf.htmlDir = publish.conf.outDir + 'original_html/' | 66 publish.conf.htmlDir = publish.conf.outDir + 'original_html/' |
66 | 67 |
67 // is source output is suppressed, just display the links to the source file | 68 // is source output is suppressed, just display the links to the source file |
68 if (JSDOC.opt.s && defined(Link) && Link.prototype._makeSrcLink) { | 69 if (JSDOC.opt.s && defined(Link) && Link.prototype._makeSrcLink) { |
69 Link.prototype._makeSrcLink = function(srcFilePath) { | 70 Link.prototype._makeSrcLink = function(srcFilePath) { |
70 return '<' + srcFilePath + '>'; | 71 return '<' + srcFilePath + '>'; |
71 } | 72 } |
72 } | 73 } |
73 | 74 |
(...skipping 13 matching lines...) Expand all Loading... |
87 try { | 88 try { |
88 var templatesDir = publish.conf.templatesDir; | 89 var templatesDir = publish.conf.templatesDir; |
89 var classTemplate = new JSDOC.JsPlate(templatesDir + 'class.tmpl'); | 90 var classTemplate = new JSDOC.JsPlate(templatesDir + 'class.tmpl'); |
90 var membersTemplate = new JSDOC.JsPlate(templatesDir + 'members.tmpl'); | 91 var membersTemplate = new JSDOC.JsPlate(templatesDir + 'members.tmpl'); |
91 var classTreeTemplate = new JSDOC.JsPlate(templatesDir + 'classtree.tmpl'); | 92 var classTreeTemplate = new JSDOC.JsPlate(templatesDir + 'classtree.tmpl'); |
92 var fileListTemplate = new JSDOC.JsPlate(templatesDir + 'filelist.tmpl'); | 93 var fileListTemplate = new JSDOC.JsPlate(templatesDir + 'filelist.tmpl'); |
93 var annotatedTemplate = new JSDOC.JsPlate(templatesDir + 'annotated.tmpl'); | 94 var annotatedTemplate = new JSDOC.JsPlate(templatesDir + 'annotated.tmpl'); |
94 var namespacesTemplate = new JSDOC.JsPlate(templatesDir + | 95 var namespacesTemplate = new JSDOC.JsPlate(templatesDir + |
95 'namespaces.tmpl'); | 96 'namespaces.tmpl'); |
96 } catch(e) { | 97 } catch(e) { |
97 print('Couldn\'t create the required templates: ' + e); | 98 generateError('Couldn\'t create the required templates: ' + e); |
98 quit(); | 99 System.exit(1); |
99 } | 100 } |
100 | 101 |
101 // some ustility filters | 102 // some ustility filters |
102 function hasNoParent($) {return ($.memberOf == '')} | 103 function hasNoParent($) {return ($.memberOf == '')} |
103 function isaFile($) {return ($.is('FILE'))} | 104 function isaFile($) {return ($.is('FILE'))} |
104 function isaClass($) {return ($.is('CONSTRUCTOR') || $.isNamespace)} | 105 function isaClass($) {return ($.is('CONSTRUCTOR') || $.isNamespace)} |
105 | 106 |
106 // get an array version of the symbolset, useful for filtering | 107 // get an array version of the symbolset, useful for filtering |
107 var symbols = symbolSet.toArray(); | 108 var symbols = symbolSet.toArray(); |
108 | 109 |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
165 var fileList = fileListTemplate.process(symbols); | 166 var fileList = fileListTemplate.process(symbols); |
166 IO.saveFile(publish.conf.outDir, 'filelist.html', fileList); | 167 IO.saveFile(publish.conf.outDir, 'filelist.html', fileList); |
167 | 168 |
168 var annotated = annotatedTemplate.process(classes); | 169 var annotated = annotatedTemplate.process(classes); |
169 IO.saveFile(publish.conf.outDir, 'annotated' + publish.conf.ext, annotated); | 170 IO.saveFile(publish.conf.outDir, 'annotated' + publish.conf.ext, annotated); |
170 IO.saveFile(publish.conf.htmlDir, 'annotated.html', annotated); | 171 IO.saveFile(publish.conf.htmlDir, 'annotated.html', annotated); |
171 | 172 |
172 var namespaces = namespacesTemplate.process(classes); | 173 var namespaces = namespacesTemplate.process(classes); |
173 IO.saveFile(publish.conf.outDir, 'namespaces' + publish.conf.ext, namespaces); | 174 IO.saveFile(publish.conf.outDir, 'namespaces' + publish.conf.ext, namespaces); |
174 IO.saveFile(publish.conf.htmlDir, 'namespaces.html', namespaces); | 175 IO.saveFile(publish.conf.htmlDir, 'namespaces.html', namespaces); |
| 176 |
| 177 if (g_numErrors > 0) { |
| 178 print('Num Errors: ' + g_numErrors); |
| 179 System.exit(1); |
| 180 } |
175 } | 181 } |
176 | 182 |
177 | 183 |
178 /** | 184 /** |
179 * Gets just the first sentence (up to a full stop). | 185 * Gets just the first sentence (up to a full stop). |
180 * Should not break on dotted variable names. | 186 * Should not break on dotted variable names. |
181 * @param {string} desc Description to extract summary from. | 187 * @param {string} desc Description to extract summary from. |
182 * @return {string} summary. | 188 * @return {string} summary. |
183 */ | 189 */ |
184 function summarize(desc) { | 190 function summarize(desc) { |
(...skipping 245 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
430 function camelCaseToUnderscore(str) { | 436 function camelCaseToUnderscore(str) { |
431 function toUnderscore(match) { | 437 function toUnderscore(match) { |
432 return '_' + match.toLowerCase(); | 438 return '_' + match.toLowerCase(); |
433 } | 439 } |
434 return str[0].toLowerCase() + | 440 return str[0].toLowerCase() + |
435 str.substring(1).replace(/[A-Z]/g, toUnderscore); | 441 str.substring(1).replace(/[A-Z]/g, toUnderscore); |
436 } | 442 } |
437 | 443 |
438 /** | 444 /** |
439 * Prints a warning about an unknown type only once. | 445 * Prints a warning about an unknown type only once. |
| 446 * @param {string} place Use to print error message if type not found. |
440 * @param {string} type Type specification. | 447 * @param {string} type Type specification. |
441 */ | 448 */ |
442 function reportUnknownType(type) { | 449 function reportUnknownType(place, type) { |
443 if (!g_unknownTypes[type]) { | 450 if (!g_unknownTypes[type]) { |
444 g_unknownTypes[type] = true; | 451 g_unknownTypes[type] = true; |
445 print ('WARNING: reference to unknown type: "' + type + '"'); | 452 generatePlaceError (place, 'reference to unknown type: "' + type + '"'); |
446 } | 453 } |
447 } | 454 } |
448 | 455 |
449 /** | 456 /** |
450 * Gets index of closing character. | 457 * Gets index of closing character. |
451 * @param {string} str string to search. | 458 * @param {string} str string to search. |
452 * @param {number} startIndex index to start searching at. Must be an opening | 459 * @param {number} startIndex index to start searching at. Must be an opening |
453 * character. | 460 * character. |
454 * @return {number} Index of closing character or (-1) if not found. | 461 * @return {number} Index of closing character or (-1) if not found. |
455 */ | 462 */ |
(...skipping 30 matching lines...) Expand all Loading... |
486 return startIndex; | 493 return startIndex; |
487 } | 494 } |
488 } | 495 } |
489 } | 496 } |
490 ++startIndex; | 497 ++startIndex; |
491 } | 498 } |
492 return -1; | 499 return -1; |
493 } | 500 } |
494 | 501 |
495 /** | 502 /** |
| 503 * Make's a name by concatenating strings. |
| 504 * @param {...[string]} strings to concatenate. |
| 505 * @return {string} Concatenated string. |
| 506 */ |
| 507 function makeName() { |
| 508 var str = ''; |
| 509 for (var ii = 0; ii < arguments.length; ++ii) { |
| 510 if (str) { |
| 511 str += '.'; |
| 512 } |
| 513 str += arguments[ii]; |
| 514 } |
| 515 return str; |
| 516 } |
| 517 |
| 518 /** |
| 519 * Generates an error msg. |
| 520 * @param {string} msg. |
| 521 */ |
| 522 function generateError(msg) { |
| 523 ++g_numErrors; |
| 524 print('ERROR: ' + msg); |
| 525 } |
| 526 |
| 527 /** |
| 528 * Generates an error msg. |
| 529 * @param {string} place Use to print error message. |
| 530 * @param {string} msg. |
| 531 */ |
| 532 function generatePlaceError(place, msg) { |
| 533 generateError(place + ': ' + msg); |
| 534 } |
| 535 |
| 536 /** |
496 * Converts a reference to a single JSDOC type specification to an html link. | 537 * Converts a reference to a single JSDOC type specification to an html link. |
| 538 * @param {string} place Use to print error message if type not found. |
497 * @param {string} str to linkify. | 539 * @param {string} str to linkify. |
498 * @return {string} linkified string. | 540 * @return {string} linkified string. |
499 */ | 541 */ |
500 function linkifySingleType(type) { | 542 function linkifySingleType(place, type) { |
501 var not = ''; | 543 var not = ''; |
502 var equals = ''; | 544 var equals = ''; |
503 // Remove ! if it exists. | 545 // Remove ! if it exists. |
504 if (type[0] == '!') { | 546 if (type[0] == '!') { |
505 not = '!' | 547 not = '!' |
506 type = type.substring(1); | 548 type = type.substring(1); |
507 } | 549 } |
508 if (endsWith(type, '=')) { | 550 if (endsWith(type, '=')) { |
509 equals = '='; | 551 equals = '='; |
510 type = type.substring(0, type.length - 1); | 552 type = type.substring(0, type.length - 1); |
511 } | 553 } |
512 | 554 |
513 var link = type; | 555 var link = type; |
514 | 556 |
515 // Check for array wrapper. | 557 // Check for array wrapper. |
516 if (startsWith(type, 'Array.<')) { | 558 if (startsWith(type, 'Array.<')) { |
517 var closingAngle = getIndexOfClosingCharacter(type, 6); | 559 var closingAngle = getIndexOfClosingCharacter(type, 6); |
518 if (closingAngle < 0) { | 560 if (closingAngle < 0) { |
519 print ('WARNING: Unmatched "<" in Array type : ' + type); | 561 generatePlaceError(place, 'Unmatched "<" in Array type : ' + type); |
520 } else { | 562 } else { |
521 link = 'Array.<' + | 563 link = 'Array.<' + |
522 linkifySingleType(type.substring(7, closingAngle)) + '>'; | 564 linkifySingleType(place, type.substring(7, closingAngle)) + '>'; |
523 } | 565 } |
524 } else if (startsWith(type, 'function(')) { | 566 } else if (startsWith(type, 'function(')) { |
525 var closingParen = getIndexOfClosingCharacter(type, 8); | 567 var closingParen = getIndexOfClosingCharacter(type, 8); |
526 if (closingParen < 0) { | 568 if (closingParen < 0) { |
527 print ('WARNING: Unmatched "(" in function type : ' + type); | 569 generatePlaceError(place, 'Unmatched "(" in function type : ' + type); |
528 } else { | 570 } else { |
529 var end = type.substring(closingParen + 1); | 571 var end = type.substring(closingParen + 1); |
530 if (!startsWith(end, ': ')) { | 572 if (!startsWith(end, ': ')) { |
531 print ('WARNING: Malformed return specification on function. Must be' + | 573 generatePlaceError(place, |
532 ' "function(args): type" including the space after the colon.'); | 574 'Malformed return specification on function. Must be' + |
| 575 ' "function(args): type" including the space after the colon.'); |
533 } else { | 576 } else { |
534 var args = type.substring(9, closingParen).split(/ *, */); | |
535 var output = ''; | 577 var output = ''; |
536 for (var ii = 0; ii < args.length; ++ii) { | 578 var argsStr = type.substring(9, closingParen); |
537 if (ii > 0) { | 579 if (argsStr) { |
538 output += ', '; | 580 var args = argsStr.split(/ *, */); |
| 581 for (var ii = 0; ii < args.length; ++ii) { |
| 582 if (ii > 0) { |
| 583 output += ', '; |
| 584 } |
| 585 output += linkifyTypeSpec(place, args[ii]); |
539 } | 586 } |
540 output += linkifyTypeSpec(args[ii]); | |
541 } | 587 } |
542 link = 'function(' + output + '): ' + linkifyTypeSpec(end.substring(2)); | 588 link = 'function(' + output + '): ' + |
| 589 linkifyTypeSpec(place, end.substring(2)); |
543 } | 590 } |
544 } | 591 } |
545 } else if (type.indexOf(':') >= 0) { // check for records. | 592 } else if (type.indexOf(':') >= 0) { // check for records. |
546 var elements = type.split(/\s*,\s*/); | 593 if (type.indexOf('::') >= 0) { // check for CPP scope |
547 var output = '{'; | 594 generatePlaceError(place, |
548 for (var ii = 0; ii < elements.length; ++ii) { | 595 'CPP "::" scope operator found for type "' + type + |
549 if (ii > 0) { | 596 '" must be Javascript "." scope operator.'); |
550 output += ', '; | 597 } else { |
| 598 var elements = type.split(/\s*,\s*/); |
| 599 var output = '{'; |
| 600 for (var ii = 0; ii < elements.length; ++ii) { |
| 601 if (ii > 0) { |
| 602 output += ', '; |
| 603 } |
| 604 var element = elements[ii]; |
| 605 var colon = element.indexOf(': '); |
| 606 if (colon < 0) { |
| 607 generatePlaceError(place, |
| 608 'Malformed record specification. Format must be ' + |
| 609 '{id1: type1, id2: type2, ...}.'); |
| 610 output += element; |
| 611 } else { |
| 612 var name = element.substring(0, colon); |
| 613 var subType = element.substring(colon + 2); |
| 614 output += name + ': ' + linkifyTypeSpec(place, subType) |
| 615 } |
551 } | 616 } |
552 var element = elements[ii]; | 617 link = output + '}'; |
553 var colon = element.indexOf(': '); | |
554 if (colon < 0) { | |
555 print ("WARNING: Malformed record specification. Format must be " + | |
556 "{id1: type1, id2: type2, ...}."); | |
557 output += element; | |
558 } else { | |
559 var name = element.substring(0, colon); | |
560 var subType = element.substring(colon + 2); | |
561 output += name + ': ' + linkifyTypeSpec(subType) | |
562 } | |
563 } | 618 } |
564 link = output + '}'; | |
565 } else { | 619 } else { |
566 var symbol = getSymbol(type); | 620 var symbol = getSymbol(type); |
567 if (symbol) { | 621 if (symbol) { |
568 link = '<a class="el" href="' + getLinkToSymbol(symbol) + '">' + | 622 link = '<a class="el" href="' + getLinkToSymbol(symbol) + '">' + |
569 type + '</a>'; | 623 type + '</a>'; |
570 } else if (startsWith(type, 'o3d.')) { | |
571 // TODO: remove this hack, make nixysa generate JSDOC js | |
572 // files instead of C++ headers and pass those into | |
573 // jsdoctoolkit. | |
574 reportUnknownType(type); | |
575 link = '<a class="el" href="../classo3d_1_1_' + | |
576 camelCaseToUnderscore(type.substring(4)) + '.html">' + | |
577 type + '</a>'; | |
578 } else { | 624 } else { |
579 // See if the symbol is a property or field. | 625 // See if the symbol is a property or field. |
580 var period = type.lastIndexOf('.'); | 626 var period = type.lastIndexOf('.'); |
581 if (period >= 0 && type != '...') { | 627 if (period >= 0 && type != '...') { |
582 var subType = type.substring(0, period); | 628 var subType = type.substring(0, period); |
583 symbol = getSymbol(subType); | 629 symbol = getSymbol(subType); |
584 if (symbol) { | 630 if (symbol) { |
585 var field = type.substring(period + 1); | 631 var field = type.substring(period + 1); |
586 link = '<a class="el" href="' + getLinkToSymbol(symbol) + '#' + | 632 link = '<a class="el" href="' + getLinkToSymbol(symbol) + '#' + |
587 field + '">' + type + '</a>'; | 633 field + '">' + type + '</a>'; |
588 } else { | 634 } else { |
589 if (subType[0] == '?') { | 635 if (startsWith(type, 'o3d.')) { |
590 subType = subType.substring(1); | 636 // TODO(gman): remove this hack, make nixysa generate JSDOC js |
591 } | 637 // files instead of C++ headers and pass those into |
592 if (!g_validJSDOCTypes[subType]) { | 638 // jsdoctoolkit. |
593 reportUnknownType(type); | 639 reportUnknownType(place, type); |
| 640 link = '<a class="el" href="../classo3d_1_1_' + |
| 641 camelCaseToUnderscore(type.substring(4)) + '.html">' + |
| 642 type + '</a>'; |
| 643 } else { |
| 644 if (subType[0] == '?') { |
| 645 subType = subType.substring(1); |
| 646 } |
| 647 if (!g_validJSDOCTypes[subType]) { |
| 648 reportUnknownType(place, type); |
| 649 } |
594 } | 650 } |
595 } | 651 } |
596 } | 652 } |
597 } | 653 } |
598 } | 654 } |
599 | 655 |
600 return not + link + equals; | 656 return not + link + equals; |
601 } | 657 } |
602 | 658 |
603 /** | 659 /** |
(...skipping 16 matching lines...) Expand all Loading... |
620 } else { | 676 } else { |
621 return str.replace(/\|/g, ', '); | 677 return str.replace(/\|/g, ', '); |
622 } | 678 } |
623 } | 679 } |
624 | 680 |
625 /** | 681 /** |
626 * Converts a JSDOC type specification into html links. For example | 682 * Converts a JSDOC type specification into html links. For example |
627 * '(!o3djs.math.Vector3|!O3D.math.Vector4)' would change to | 683 * '(!o3djs.math.Vector3|!O3D.math.Vector4)' would change to |
628 * '(!<a href="??">o3djs.math.Vector3</a> | 684 * '(!<a href="??">o3djs.math.Vector3</a> |
629 * |!<a href="??">o3djs.math.Vector4</a>)'. | 685 * |!<a href="??">o3djs.math.Vector4</a>)'. |
| 686 * @param {string} place Use to print error message if type not found. |
630 * @param {string} str to linkify. | 687 * @param {string} str to linkify. |
631 * @return {string} linkified string. | 688 * @return {string} linkified string. |
632 */ | 689 */ |
633 function linkifyTypeSpec(str) { | 690 function linkifyTypeSpec(place, str) { |
634 var output = ''; | 691 var output = ''; |
635 if (str) { | 692 if (str) { |
636 var fixed = fixSpecCommas(str); | 693 var fixed = fixSpecCommas(str); |
637 // TODO: needs to split outside of parens and angle brackets. | 694 // TODO: needs to split outside of parens and angle brackets. |
638 if (startsWith(fixed, '(') && endsWith(fixed, ')')) { | 695 if (startsWith(fixed, '(') && endsWith(fixed, ')')) { |
639 var types = fixed.substring(1, fixed.length - 1).split('|'); | 696 var types = fixed.substring(1, fixed.length - 1).split('|'); |
640 output += '('; | 697 output += '('; |
641 for (var tt = 0; tt < types.length; ++tt) { | 698 for (var tt = 0; tt < types.length; ++tt) { |
642 if (tt > 0) { | 699 if (tt > 0) { |
643 output += '|'; | 700 output += '|'; |
644 } | 701 } |
645 output += linkifySingleType(types[tt]); | 702 output += linkifySingleType(place, types[tt]); |
646 } | 703 } |
647 output += ')'; | 704 output += ')'; |
648 } else { | 705 } else { |
649 output += linkifySingleType(fixed); | 706 output += linkifySingleType(place, fixed); |
650 } | 707 } |
| 708 } else { |
| 709 generatePlaceError(place, 'missing type specification (' + str + ')'); |
651 } | 710 } |
652 return output; | 711 return output; |
653 } | 712 } |
654 | 713 |
655 /** | 714 /** |
| 715 * Same as linkifyTypeSpec but allows str to be undefined or ''. |
| 716 * @param {string} place Use to print error message if type not found. |
| 717 * @param {string} str to linkify. |
| 718 * @return {string} linkified string. |
| 719 */ |
| 720 function linkifyTypeSpecForReturn(place, str) { |
| 721 if (str) { |
| 722 return linkifyTypeSpec(place, str); |
| 723 } |
| 724 return ''; |
| 725 } |
| 726 |
| 727 /** |
656 * Gets a symbol for a type. | 728 * Gets a symbol for a type. |
657 * This is here mostly for debugging so you can insert a print before or after | 729 * This is here mostly for debugging so you can insert a print before or after |
658 * each call to g_symbolSet.getSymbol. | 730 * each call to g_symbolSet.getSymbol. |
659 * @param {string} type fully qualified type. | 731 * @param {string} type fully qualified type. |
660 * @return {Symbol} The symbol object for the type or null if not found. | 732 * @return {Symbol} The symbol object for the type or null if not found. |
661 */ | 733 */ |
662 function getSymbol(type) { | 734 function getSymbol(type) { |
663 return g_symbolSet.getSymbol(type); | 735 return g_symbolSet.getSymbol(type); |
664 } | 736 } |
665 | 737 |
666 /** | 738 /** |
667 * Gets the source path for a symbol starting from 'o3djs/' | 739 * Gets the source path for a symbol starting from 'o3djs/' |
668 * @param {!Symbol} symbol The symbol to get the source name for. | 740 * @param {!Symbol} symbol The symbol to get the source name for. |
669 * @return {string} The name of the source file. | 741 * @return {string} The name of the source file. |
670 */ | 742 */ |
671 function getSourcePath(symbol) { | 743 function getSourcePath(symbol) { |
672 var path = symbol.srcFile.replace(/\\/g, '/'); | 744 var path = symbol.srcFile.replace(/\\/g, '/'); |
673 var index = path.indexOf('/o3djs/'); | 745 var index = path.indexOf('/o3djs/'); |
674 return path.substring(index + 1); | 746 return path.substring(index + 1); |
675 } | 747 } |
OLD | NEW |