Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 | 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 2625x 2625x 218x 218x 2407x 2407x 2407x 2625x 19x 19x 19x 19x 19x 19x 2407x 2407x 2625x 2359x 2359x 2407x 2407x 2625x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 912x 3x 3x 909x 909x 909x 909x 912x 1493x 1493x 14x 14x 1479x 1479x 895x 895x 895x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 145x 3x 3x 145x 15x 15x 15x 142x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 2112x 2112x 2112x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 3768x 52x 52x 3736x 3768x 3768x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 1410x 2x 2x 1408x 1408x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 1769x 7x 7x 1762x 1762x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 1928x 79x 79x 79x 1849x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 17x 17x 17x 17x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 1053x 150x 150x 903x 903x 1053x 885x 885x 18x 18x 18x 41x 14x 5x 5x 9x 9x 9x 14x 4x 4x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 311x 311x 311x 311x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 59x 2x 2x 57x 59x 59x 59x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 161x 161x 161x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 60x 60x 60x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 31x 31x 31x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 30x 30x 30x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 40x 40x 40x 40x 40x 40x 40x 40x 40x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 29x 29x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 29x 29x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 12x 12x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 49x 49x 49x 49x 49x 49x 49x 133x 133x 133x 133x 133x 133x 133x 133x 133x 180x 8x 8x 172x 172x 133x 133x 133x 133x 133x 133x 133x 133x 133x 27x 27x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 57x 7x 7x 50x 50x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 390x 4x 4x 386x 390x 278x 143x 143x 143x 278x 243x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 114x 4x 4x 110x 114x 84x 27x 27x 27x 84x 83x 133x 133x 133x 133x 133x 133x 133x 133x 133x 356x 88x 88x 268x 268x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 3388x 3056x 3056x 332x 332x 332x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 362x 88x 88x 274x 274x 274x 133x 133x 133x 133x 133x 133x 133x 133x 133x 358x 358x 358x 344x 358x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 122x 6x 6x 116x 116x 116x 116x 116x 116x 116x 122x 51x 51x 65x 65x 65x 65x 65x 65x 122x 12x 12x 12x 53x 53x 53x 53x 53x 53x 53x 75x 39x 39x 39x 39x 14x 14x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 99x 99x 99x 14x 14x 85x 85x 85x 85x 85x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 12x 12x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 63x 63x 63x 63x 63x 63x 63x 63x 63x 63x 63x 63x 63x 63x 63x 63x 63x 63x 63x 63x 19x 19x 17x 17x 17x 19x 63x 63x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 41x 41x 41x 41x 41x 8x 8x 8x 8x 8x 8x 33x 33x 33x 33x 41x 29x 29x 29x 29x 19x 19x 29x 29x 21x 21x 21x 21x 21x 8x 8x 8x 8x 8x 8x 8x 8x 8x 4x 4x 4x 4x 4x 4x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 72x 72x 72x 72x 72x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 7x 7x 7x 7x 7x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 644x 644x 644x 5x 5x 639x 644x 644x 644x 1423x 93x 164x 93x 1423x 431x 431x 1423x 639x 639x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 1885x 1141x 1141x 744x 744x 1885x 205x 205x 539x 539x 1885x 1x 1x 538x 538x 1885x 1674x 1674x 538x 133x 133x 133x 133x 133x 133x 133x 133x 133x 320x 320x 211x 211x 109x 109x 320x 2x 2x 107x 107x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 133x 253x 165x 165x 88x 88x 88x 88x 253x 3x 4x 4x 3x 88x 88x 253x 133x 133x 133x | /*!
* WikiLambda Vue ZObject handling utilities.
*
* This contains methods to navigate a ZObject passed as input through their keys and
* values. Those functions that are commonly used for read operations on the ZObject
* persisted in the store are also added in the ZObject composable.
*
* These methods can be used with canonical and hybrid form, so that they can
* be safely used for zobjects fetched and stored in the library (which are
* in canonical form) and for the root ZObject represented in the page (which
* is in hybrid form). Tests must reflect this by testing all use cases in both
* forms.
*
* @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt
* @license MIT
*/
const Constants = require( '../Constants.js' );
const { extractWarningsData } = require( './errorUtils.js' );
const { getValueFromCanonicalZMap, hybridToCanonical } = require( './schemata.js' );
const { isValidZidFormat, getScaffolding } = require( './typeUtils.js' );
// Stable, position-independent identity for typed-list items. A list item's
// object reference survives reordering (moveListItemByKeyPath splices the same
// object), so a WeakMap keyed on it yields a Vue key that travels with the item
// rather than with its array index. See getListItemKey below.
const listItemKeys = new WeakMap();
let listItemKeyCounter = 0;
const zobjectUtils = {
/**
* Gets the type of a valid ZObject.
* The input object can be canonical or hybrid, it converts the object to canonical first.
* The output object will always be in canonical form
*
* @param {Object|Array|string} value
* @return {string|Object|undefined}
*/
getZObjectType: function ( value ) {
// If value is a string; infer type from syntax
if ( typeof value === 'string' ) {
return isValidZidFormat( value ) ? Constants.Z_REFERENCE : Constants.Z_STRING;
}
let type;
// If value is an Array; return typed list definition
if ( Array.isArray( value ) ) {
type = {
[ Constants.Z_OBJECT_TYPE ]: Constants.Z_FUNCTION_CALL,
[ Constants.Z_FUNCTION_CALL_FUNCTION ]: Constants.Z_TYPED_LIST,
[ Constants.Z_TYPED_LIST_TYPE ]: hybridToCanonical( value[ 0 ] ) || Constants.Z_OBJECT
};
}
// If value has a type key Z1K1; return value of Z1K1
if ( !!value && Constants.Z_OBJECT_TYPE in value ) {
type = hybridToCanonical( value[ Constants.Z_OBJECT_TYPE ] );
}
// If undefined or null or non-valid; return undefined
return type || undefined;
},
/**
* Walks a nested zobject given an array of keys and returns a
* reference to the parent object and the last key from the path.
*
* This method is used to walk the zobject from the store in order
* to perform mutations.
*
* When navigating the path, it throws exceptions when it can't find
* the final target. While performing mutation actions, it is recommended
* to throw these exceptions, but while using it for getters, exceptions
* can be caught to return undefined or empty values.
*
* E.g. Given the path [ 'main', 'parent', 'key' ] and the following object:
* ```
* {
* granduncle: { ... },
* main: {
* aunt: { ... },
* parent: {
* key: 'terminal value',
* sibling: 'some sibling value'
* }
* }
* }
* ```
* Will return the final key "key" and the reference to the object:
* ```
* {
* key: 'terminal value',
* sibling: 'some sibling value'
* }
* ```
* To then mutate the terminal value for key, do:
* ```
* target[ finalKey ] = 'new value';
* ```
*
* @param {Object|Array} root
* @param {Array} keyPath
* @return {Object}
*/
resolveZObjectByKeyPath: function ( root, keyPath ) {
if ( !Array.isArray( keyPath ) || keyPath.length === 0 ) {
throw new Error( 'Unable to resolve key path: Key path must be a non-empty array' );
}
const lastKeyIndex = keyPath.length - 1;
let target = root;
for ( let i = 0; i < lastKeyIndex; i++ ) {
const key = keyPath[ i ];
if ( !( key in target ) || typeof target[ key ] !== 'object' || target[ key ] === null ) {
throw new Error( `Unable to resolve key path: Key path points to a non-object at "${ key }"` );
}
target = target[ key ];
}
const finalKey = keyPath[ lastKeyIndex ];
return { target, finalKey };
},
/**
* Returns a stable, position-independent Vue key for a typed-list item.
*
* List items are addressed positionally (there is no per-item id stored in
* the ZObject), but keying a `v-for` by position makes Vue reuse component
* instances in place on reorder, leaving their local UI state (expansion,
* rendered-value caches) paired with the wrong item (T431714). Because a
* reorder splices the *same* item object to its new index, a WeakMap keyed
* on the item object gives an identity that travels with it: Vue relocates
* the existing instance (and its state) to the new position instead of
* reusing it in place. The returned key must NOT include the index, or the
* move would recreate the instance rather than relocate it.
*
* Terminal (primitive) items can't key a WeakMap and carry no relocatable
* state, so they fall back to a positional key (kept distinct from the
* object-identity keys by prefix).
*
* @param {Object|Array|string|number} item
* @param {number|string} index Position of the item, used only for the
* primitive fallback.
* @return {string}
*/
getListItemKey: function ( item, index ) {
if ( item === null || typeof item !== 'object' ) {
return `item-${ index }`;
}
if ( !listItemKeys.has( item ) ) {
listItemKeyCounter += 1;
listItemKeys.set( item, listItemKeyCounter );
}
return `item-id-${ listItemKeys.get( item ) }`;
},
/**
* Returns the terminal value of a string object.
* Accepts both canonical and hybrid forms.
*
* @param {Object|string} value
* @return {string|undefined}
*/
getZStringTerminalValue: function ( value ) {
return ( typeof value === 'object' ) ?
value[ Constants.Z_STRING_VALUE ] :
value;
},
/**
* Returns the terminal value of a reference object.
* Accepts both canonical and hybrid.
*
* @param {Object|string} value
* @return {string|undefined}
*/
getZReferenceTerminalValue: function ( value ) {
if ( !value ) {
return undefined;
}
return ( typeof value === 'object' ) ?
value[ Constants.Z_REFERENCE_ID ] :
value;
},
/**
* Returns the terminal text value of a monolingual string object.
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @return {string|undefined}
*/
getZMonolingualTextValue: function ( value ) {
if ( !value || typeof value !== 'object' ) {
return undefined;
}
const monoText = value[ Constants.Z_MONOLINGUALSTRING_VALUE ];
return zobjectUtils.getZStringTerminalValue( monoText );
},
/**
* Returns the terminal language value of a monolingual string object.
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @return {string|undefined}
*/
getZMonolingualLangValue: function ( value ) {
if ( !value || typeof value !== 'object' ) {
return undefined;
}
const monoLang = value[ Constants.Z_MONOLINGUALSTRING_LANGUAGE ];
return zobjectUtils.getZLangTerminalValue( monoLang );
},
/**
* Returns the terminal value of a language object.
* Accepts both canonical and hybrid.
*
* @param {Object|string} value
* @return {string|undefined}
*/
getZLangTerminalValue: function ( value ) {
if ( value && typeof value === 'object' && Constants.Z_NATURAL_LANGUAGE_ISO_CODE in value ) {
const langCode = value[ Constants.Z_NATURAL_LANGUAGE_ISO_CODE ];
return zobjectUtils.getZStringTerminalValue( langCode );
}
return zobjectUtils.getZReferenceTerminalValue( value );
},
/**
* Returns the terminal value of a boolean object.
* Accepts both canonical and hybrid.
*
* @param {Object|string} value
* @return {string|undefined}
*/
getZBooleanValue: function ( value ) {
const boolId = ( value && typeof value === 'object' && Constants.Z_BOOLEAN_IDENTITY in value ) ?
value[ Constants.Z_BOOLEAN_IDENTITY ] :
value;
return zobjectUtils.getZReferenceTerminalValue( boolId );
},
/**
* Returns the terminal value of a function call function (Z7K1),
* only when it's a direct reference.
*
* If nested flag is true, returns the terminal value by:
* * if Z7K1 has an argument reference, returns its terminal value
* * if Z7K1 has a function call, returns its terminal value recursively
*
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @param {boolean} nested
* @return {string|undefined}
*/
getZFunctionCallFunctionId: function ( value, nested = false ) {
if ( !value || typeof value !== 'object' || !( Constants.Z_FUNCTION_CALL_FUNCTION in value ) ) {
return undefined;
}
const field = value[ Constants.Z_FUNCTION_CALL_FUNCTION ];
if ( typeof field === 'string' || ( Constants.Z_REFERENCE_ID in field ) ) {
return zobjectUtils.getZReferenceTerminalValue( field );
}
// If nested flag is true:
// return final value of argument reference or nested function call
if ( nested ) {
if ( Constants.Z_ARGUMENT_REFERENCE_KEY in field ) {
return zobjectUtils.getZArgumentReferenceTerminalValue( field );
}
if ( Constants.Z_FUNCTION_CALL_FUNCTION in field ) {
return zobjectUtils.getZFunctionCallFunctionId( field, true );
}
}
return undefined;
},
/**
* Returns an array with the input keys given a function call.
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @return {Array}
*/
getZFunctionCallArgumentKeys: function ( value ) {
const excludedKeys = [ Constants.Z_OBJECT_TYPE, Constants.Z_FUNCTION_CALL_FUNCTION ];
return ( value && typeof value === 'object' && Constants.Z_FUNCTION_CALL_FUNCTION in value ) ?
Object.keys( value ).filter( ( key ) => !excludedKeys.includes( key ) ) :
[];
},
/**
* Returns an array with the terminal values of a monolingual string set.
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @return {Array}
*/
getZMonolingualStringsetValues: function ( value ) {
if ( !value || typeof value !== 'object' ) {
return [];
}
const monoTexts = value[ Constants.Z_MONOLINGUALSTRINGSET_VALUE ];
return ( monoTexts && Array.isArray( monoTexts ) && monoTexts.length > 0 ) ?
monoTexts.slice( 1 ).map( ( item ) => zobjectUtils.getZStringTerminalValue( item ) ) :
[];
},
/**
* Returns the terminal language value of a monolingual stringset object.
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @return {string|undefined}
*/
getZMonolingualStringsetLang: function ( value ) {
return ( value && typeof value === 'object' ) ?
zobjectUtils.getZLangTerminalValue( value[ Constants.Z_MONOLINGUALSTRINGSET_LANGUAGE ] ) :
undefined;
},
/**
* Returns the terminal value of an argument reference object.
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @return {string|undefined}
*/
getZArgumentReferenceTerminalValue: function ( value ) {
return ( value && typeof value === 'object' ) ?
zobjectUtils.getZStringTerminalValue( value[ Constants.Z_ARGUMENT_REFERENCE_KEY ] ) :
undefined;
},
/**
* Returns the terminal value of a test target function.
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @return {string|undefined}
*/
getZTesterFunctionZid: function ( value ) {
return ( value && typeof value === 'object' ) ?
zobjectUtils.getZReferenceTerminalValue( value[ Constants.Z_TESTER_FUNCTION ] ) :
undefined;
},
/**
* Returns the terminal value of an implementation target function.
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @return {string|undefined}
*/
getZImplementationFunctionZid: function ( value ) {
return ( value && typeof value === 'object' ) ?
zobjectUtils.getZReferenceTerminalValue( value[ Constants.Z_IMPLEMENTATION_FUNCTION ] ) :
undefined;
},
/**
* Returns the key that defines the type of implementation (code,
* composition, or builtin), or undefined if there's no key.
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @return {string|undefined}
*/
getZImplementationContentType: function ( value ) {
const contentKeys = [
Constants.Z_IMPLEMENTATION_CODE,
Constants.Z_IMPLEMENTATION_COMPOSITION,
Constants.Z_IMPLEMENTATION_BUILT_IN
];
// Any implementation keys that are present and have non falsy content
const availableKeys = Object.keys( value || {} )
.filter( ( key ) => contentKeys.includes( key ) && !!value[ key ] );
return availableKeys.length === 1 ? availableKeys[ 0 ] : undefined;
},
/**
* Returns the terminal value of the code programming language,
* which will always be a reference.
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @return {string|undefined}
*/
getZCodeProgrammingLanguageId: function ( value ) {
const lang = ( value && typeof value === 'object' ) ? value[ Constants.Z_CODE_LANGUAGE ] : undefined;
return zobjectUtils.getZReferenceTerminalValue( lang );
},
/**
* Returns the terminal string value of the code.
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @return {string|undefined}
*/
getZCodeString: function ( value ) {
const code = ( value && typeof value === 'object' ) ? value[ Constants.Z_CODE_CODE ] : undefined;
return zobjectUtils.getZStringTerminalValue( code );
},
/**
* Returns the terminal string value of the HTML object.
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @return {string|undefined}
*/
getZHTMLFragmentTerminalValue: function ( value ) {
const html = ( value && typeof value === 'object' ) ? value[ Constants.Z_HTML_FRAGMENT_VALUE ] : undefined;
return zobjectUtils.getZStringTerminalValue( html );
},
/**
* Returns whether the given key object is marked as identity
* by setting Z3K4/is identity key to true.
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @return {boolean}
*/
getZKeyIsIdentity: function ( value ) {
const flag = ( value && typeof value === 'object' ) ? value[ Constants.Z_KEY_IS_IDENTITY ] : undefined;
const bool = ( flag && typeof flag === 'object' && Constants.Z_BOOLEAN_IDENTITY in flag ) ?
zobjectUtils.getZBooleanValue( flag ) :
zobjectUtils.getZReferenceTerminalValue( flag );
return bool === Constants.Z_BOOLEAN_TRUE;
},
/**
* Returns an array of all the terminal language values in an multilingual string object.
*
* @param {Object} value
* @return {Array}
*/
getZMultilingualLangs: function ( value ) {
if ( !value || typeof value !== 'object' || !( Constants.Z_MULTILINGUALSTRING_VALUE in value ) ) {
return [];
}
const monolinguals = value[ Constants.Z_MULTILINGUALSTRING_VALUE ].slice( 1 );
return monolinguals.map( ( monolingual ) => zobjectUtils.getZMonolingualLangValue( monolingual ) );
},
/**
* Returns an array of all the multilingual string objects.
*
* @param {Object} value
* @return {Array}
*/
getZMultilingualValues: function ( value ) {
if ( !value || typeof value !== 'object' || !( Constants.Z_MULTILINGUALSTRING_VALUE in value ) ) {
return [];
}
return value[ Constants.Z_MULTILINGUALSTRING_VALUE ].slice( 1 );
},
/**
* Returns an array of all the language references or language
* in the array of monolingual stringset objects.
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @return {Array}
*/
getZMultilingualStringsetLangs: function ( value ) {
if ( !value || typeof value !== 'object' || !( Constants.Z_MULTILINGUALSTRINGSET_VALUE in value ) ) {
return [];
}
const monolingualSets = value[ Constants.Z_MULTILINGUALSTRINGSET_VALUE ].slice( 1 );
return monolingualSets.map( ( monolingualSet ) => zobjectUtils.getZMonolingualStringsetLang( monolingualSet ) );
},
/**
* Find a monolingual string item in a multilingual string
* object for a given language and returns:
* * its index in the array of monolinguals (including benjamin),
* * its terminal string value.
* Or undefined if no monolingual object was found.
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @param {string} lang
* @return {Object|undefined}
*/
getZMonolingualItemForLang: function ( value, lang ) {
if ( !value || typeof value !== 'object' || !( Constants.Z_MULTILINGUALSTRING_VALUE in value ) ) {
return undefined;
}
const monolinguals = value[ Constants.Z_MULTILINGUALSTRING_VALUE ];
for ( let index = 1; index < monolinguals.length; index++ ) {
if ( zobjectUtils.getZMonolingualLangValue( monolinguals[ index ] ) === lang ) {
const foundValue = zobjectUtils.getZMonolingualTextValue( monolinguals[ index ] );
return { index, value: foundValue };
}
}
return undefined;
},
/**
* Find a monolingual string set item in a multilingual string set
* object for a given language and returns:
* * its index in the array of monolingual string sets (including benjamin),
* * its terminal string value (array of strings).
* Or undefined if no monolingual string set was found.
* Accepts both canonical and hybrid.
*
* @param {Object} value
* @param {string} lang
* @return {Object|undefined}
*/
getZMonolingualStringsetForLang: function ( value, lang ) {
if ( !value || typeof value !== 'object' || !( Constants.Z_MULTILINGUALSTRINGSET_VALUE in value ) ) {
return undefined;
}
const monolingualSets = value[ Constants.Z_MULTILINGUALSTRINGSET_VALUE ];
for ( let index = 1; index < monolingualSets.length; index++ ) {
if ( zobjectUtils.getZMonolingualStringsetLang( monolingualSets[ index ] ) === lang ) {
const foundValue = zobjectUtils.getZMonolingualStringsetValues( monolingualSets[ index ] );
return { index, value: foundValue };
}
}
return undefined;
},
/**
* Returns whether the object is a literal Wikidata Entity.
*
* @param {Object} value
* @return {boolean}
*/
isWikidataLiteral: function ( value ) {
if ( !value || typeof value !== 'object' || !( Constants.Z_OBJECT_TYPE in value ) ) {
return false;
}
const type = hybridToCanonical( value[ Constants.Z_OBJECT_TYPE ] );
return Constants.WIKIDATA_TYPES.includes( type );
},
/**
* Returns whether the object is a fetched Wikidata Entity, which
* will be shaped as a Function call to one of the Wikidata Fetch
* Functions (Z6820-Z6826).
* Assumes that the object is a function, returns false if it's not.
*
* @param {Object} value
* @return {boolean}
*/
isWikidataFetch: function ( value ) {
if ( !value || typeof value !== 'object' || !( Constants.Z_FUNCTION_CALL_FUNCTION in value ) ) {
return false;
}
const functionCallFunction = zobjectUtils.getZFunctionCallFunctionId( value, true );
return Object.keys( Constants.WIKIDATA_FETCH_FUNCTIONS )
.some( ( k ) => Constants.WIKIDATA_FETCH_FUNCTIONS[ k ] === functionCallFunction );
},
/**
* Returns whether the object is a Wikidata Reference, which
* will be a literal object of one of the Wikidata Reference types
* (Z6091-Z6096).
*
* @param {Object} value
* @return {boolean}
*/
isWikidataReference: function ( value ) {
if ( !value || typeof value !== 'object' || !( Constants.Z_OBJECT_TYPE in value ) ) {
return false;
}
const type = hybridToCanonical( value[ Constants.Z_OBJECT_TYPE ] );
return Object.keys( Constants.WIKIDATA_REFERENCE_TYPES )
.some( ( k ) => Constants.WIKIDATA_REFERENCE_TYPES[ k ] === type );
},
/**
* Returns whether the object represents a Wikidata entity,
* in any of its valid forms.
*
* @param {Object} value
* @return {boolean}
*/
isWikidataEntity: function ( value ) {
return (
zobjectUtils.isWikidataFetch( value ) ||
zobjectUtils.isWikidataReference( value ) ||
zobjectUtils.isWikidataLiteral( value )
);
},
/**
* Given an object and a simplified Wikidata entity type, returns the Wikidata entity
* reference object that contains the entity Identity.
*
* Every Wikidata entity object has a Wikidata entity reference object which contains
* its identity:
* * If the object is a Wikidata Entity Reference (e.g. type Z6091), returns itself.
* * If the object is a Wikidata Entity Type (e.g. type Z6001), returns the object
* contained in its first key (e.g. Z6001K1)
* * If the object is a Wikidata Fetch Function (e.g. type Z6821), returns the object
* contained in its first argument (e.g. Z6821K1)
*
* If the input value is not a Wikidata entity object matching with the given type
* returns undefined.
*
* @param {Object} value
* @param {string} wikidataType
* @return {Object|undefined}
*/
getWikidataEntityReference: function ( value, wikidataType ) {
if ( !value || typeof value !== 'object' || !( Constants.Z_OBJECT_TYPE in value ) ) {
return undefined;
}
// Type is either Z7/Function call, a Wikidata reference type or a Wikidata type:
const type = hybridToCanonical( value[ Constants.Z_OBJECT_TYPE ] );
// If Wikidata reference type: Assign initial value
// - Z1K1: refType
// - [refType]K1: entityId
if ( type === Constants.WIKIDATA_REFERENCE_TYPES[ wikidataType ] ) {
return value;
}
// If Wikidata type: The Wikidata entity reference is in the first key
// - Z1K1: wikidataType
// - [wikidataType]K1:
// - Z1K1: refType
// - [refType]K1: entityId
if ( type === wikidataType && Constants.WIKIDATA_TYPES.includes( type ) ) {
const identityKey = `${ type }K1`;
return value[ identityKey ];
}
// If Function call: The Wikidata entity reference is in the first argument
// - Z1K1: Z7
// - Z7K1: fetchFunction
// - [fetchFunction]K1:
// - Z1K1: refType
// - [refType]K1: entityId
if ( type === Constants.Z_FUNCTION_CALL ) {
const fetchFunction = Constants.WIKIDATA_FETCH_FUNCTIONS[ wikidataType ];
const fetchFunctionRefKey = `${ fetchFunction }K1`;
return value[ fetchFunctionRefKey ];
}
return undefined;
},
/**
* Returns the terminal Wikidata ID string value of a given Wikidata Entity object of
* a given Wikidata Entity simplified type (e.g. Z600*).
*
* If the Wikidata ID is a terminal string, but is empty/unset, returns an empty string.
*
* If the given Wikidata Entity object does NOT have a terminal string ID, returns undefined.
* * E.g. Z6001K1 contains a Z7 or a Z18
* * E.g. Z6821K1.Z6091 contains a Z7 or a Z18
*
* @param {Object} value
* @param {string} wikidataType
* @return {string|undefined}
*/
getWikidataEntityId: function ( value, wikidataType ) {
const wikidataRef = zobjectUtils.getWikidataEntityReference( value, wikidataType );
if ( !wikidataRef ) {
return undefined;
}
// Once we have the Wikidata reference, return its terminal ID
const referenceType = Constants.WIKIDATA_REFERENCE_TYPES[ wikidataType ];
const referenceTypeIdKey = `${ referenceType }K1`;
return zobjectUtils.getZStringTerminalValue( wikidataRef[ referenceTypeIdKey ] );
},
/**
* Extracts the M-ID string from a Z310 (Commons Image Data Reference) object.
* Handles both plain string and normalized ZString (Z6) forms of Z310K1.
*
* @param {Object} value A Z310 object
* @return {string} M-ID (e.g. "M68960758"), or empty string if absent
*/
getCommonsMediaId: function ( value ) {
const raw = value[ Constants.Z_COMMONS_MEDIA_REFERENCE_ID ];
return zobjectUtils.getZStringTerminalValue( raw ) || '';
},
/**
* Recursively walks a nested generic type and returns
* the field IDs and whether they are valid or not.
* Returns a flat array of validated fields [ { fieldPath, isValid } ]
*
* @param {Array} keyPath
* @param {Object} value
* @return {Array}
*/
validateGenericType: function ( keyPath, value ) {
const fields = [];
// If value is overall missing, set as invalid
if ( !value || typeof value !== 'object' ) {
fields.push( {
keyPath: keyPath.join( '.' ),
isValid: false
} );
}
// If terminal value is missing, set as invalid, else valid
const isFunctionCall = value && Constants.Z_FUNCTION_CALL_FUNCTION in value;
const terminalValue = isFunctionCall ?
zobjectUtils.getZFunctionCallFunctionId( value, true ) :
zobjectUtils.getZReferenceTerminalValue( value );
const terminalKeyPath = isFunctionCall ? [ Constants.Z_FUNCTION_CALL_FUNCTION ] : [];
fields.push( {
keyPath: [ ...keyPath, ...terminalKeyPath ].join( '.' ),
isValid: !!terminalValue
} );
// If type is a function call, recursively validate its fields
if ( isFunctionCall ) {
const argKeys = zobjectUtils.getZFunctionCallArgumentKeys( value );
for ( const key of argKeys ) {
const keyFields = zobjectUtils.validateGenericType( [ ...keyPath, key ], value[ key ] );
fields.push( ...keyFields );
}
}
return fields;
},
/**
* Recursively walks a function call and returns the
* terminal field IDs and whether they are valid or not.
* Returns a flat array of validated fields [ { fieldPath, isValid } ]
* Terminal fields are:
* * For a function call Z7, field Z7K1
* * For an argument reference Z18, field Z18K1
*
* @param {Array} keyPath
* @param {Object} value
* @return {Array}
*/
validateFunctionCall: function ( keyPath, value ) {
// If value is overall missing: return invalid
if ( !value || typeof value !== 'object' ) {
return [ {
keyPath: keyPath.join( '.' ),
isValid: false
} ];
}
// If it has an argument reference key: Validate and exit
if ( Constants.Z_ARGUMENT_REFERENCE_KEY in value ) {
const terminalArgRef = zobjectUtils.getZArgumentReferenceTerminalValue( value );
return [ {
keyPath: [ ...keyPath, Constants.Z_ARGUMENT_REFERENCE_KEY ].join( '.' ),
isValid: !!terminalArgRef
} ];
}
// If it has a function call function key:
// * If it contains a reference: Validate and exit
// * If it contains an object: recurse
if ( Constants.Z_FUNCTION_CALL_FUNCTION in value ) {
const parentKeyPath = [ ...keyPath, Constants.Z_FUNCTION_CALL_FUNCTION ];
let terminalRef = value[ Constants.Z_FUNCTION_CALL_FUNCTION ];
if ( ( typeof terminalRef === 'object' ) && ( Constants.Z_REFERENCE_ID in terminalRef ) ) {
terminalRef = zobjectUtils.getZReferenceTerminalValue( terminalRef );
}
if ( typeof terminalRef !== 'object' ) {
return [ {
keyPath: parentKeyPath.join( '.' ),
isValid: !!terminalRef
} ];
}
const childFields = zobjectUtils.validateFunctionCall( parentKeyPath, terminalRef );
const hasErrors = childFields.some( ( field ) => !field.isValid );
const parentField = {
keyPath: parentKeyPath.join( '.' ),
isValid: !hasErrors
};
return [ parentField, ...childFields ];
}
// If it has no Z18K1 nor Z7K1: return invalid
return [ {
keyPath: keyPath.join( '.' ),
isValid: false
} ];
},
/**
* Creates a parser call ZObject.
*
* {
* Z1K1: Z7,
* Z7K1: <parserZid>,
* <parserZid>K1: <zobject>,
* <parserZid>K2: <zlang>
* }
*
* @param {Object} payload
* @param {string} payload.parserZid
* @param {any} payload.zobject
* @param {string} payload.zlang
* @return {Object}
*/
createParserCall: function ( { parserZid, zobject, zlang } ) {
const parserCall = getScaffolding( Constants.Z_FUNCTION_CALL );
parserCall[ Constants.Z_FUNCTION_CALL_FUNCTION ] = parserZid;
parserCall[ `${ parserZid }K1` ] = zobject;
parserCall[ `${ parserZid }K2` ] = zlang;
return parserCall;
},
/**
* Creates a renderer call ZObject.
*
* {
* Z1K1: Z7,
* Z7K1: <rendererZid>,
* <rendererZid>K1: <zobject>,
* <rendererZid>K2: <zlang>
* }
*
* @param {Object} payload
* @param {string} payload.rendererZid
* @param {any} payload.zobject
* @param {string} payload.zlang
* @return {Object}
*/
createRendererCall: function ( { rendererZid, zobject, zlang } ) {
const rendererCall = getScaffolding( Constants.Z_FUNCTION_CALL );
rendererCall[ Constants.Z_FUNCTION_CALL_FUNCTION ] = rendererZid;
rendererCall[ `${ rendererZid }K1` ] = zobject;
rendererCall[ `${ rendererZid }K2` ] = zlang;
return rendererCall;
},
/**
* Generic recursive walker for ZObjects.
* Calls a callback for every object visited and returns the result of this
* callback.
*
* @param {Object} obj - The current object
* @param {Array} path - The current key path
* @param {Function} visitor - A function (obj, path) => any
* @return {Array} collected results from the visitor
*/
walkZObject: function ( obj, path, visitor ) {
const results = [];
if ( !obj || typeof obj !== 'object' ) {
return results;
}
results.push( ...( visitor( obj, path ) || [] ) );
for ( const [ key, value ] of Object.entries( obj ) ) {
if ( Array.isArray( value ) ) {
value.forEach( ( item, i ) => {
results.push( ...zobjectUtils.walkZObject( item, [ ...path, key, i ], visitor ) );
} );
} else if ( value && typeof value === 'object' ) {
results.push( ...zobjectUtils.walkZObject( value, [ ...path, key ], visitor ) );
}
}
return results;
},
/**
* Recursively walks a ZObject and replaces nodes that match a predicate.
* Mutates the object in place.
*
* @param {Object} obj
* @param {Function} matcher - (node) => boolean, true if the node should be replaced
* @param {Function} transformer - (node) => Object, returns the transformed value
* @return {Object}
*/
walkAndTransformZObject: function ( obj, matcher, transformer ) {
if ( !obj || typeof obj !== 'object' ) {
return obj;
}
// Check if the input object matches the criteria for transformation
if ( matcher( obj ) ) {
return transformer( obj );
}
// Else, walk array items or object key-values
if ( Array.isArray( obj ) ) {
return obj.map( ( item ) => zobjectUtils.walkAndTransformZObject( item, matcher, transformer ) );
}
const result = {};
for ( const [ key, value ] of Object.entries( obj ) ) {
result[ key ] = zobjectUtils.walkAndTransformZObject( value, matcher, transformer );
}
return result;
},
/**
* Returns whether the given test metadata is in a pending state,
* i.e. the given metadata map contains a 'pending' key
*
* @param {Object} metadata
* @return {boolean}
*/
hasPendingMetadata: function ( metadata ) {
// No metadata yet, test has not run, return false
if ( !metadata || !metadata.K1 ) {
return false;
}
const keys = metadata.K1;
if ( !Array.isArray( keys ) || keys.length <= 1 ) {
return false;
}
return keys.slice( 1 ).some( ( entry ) => entry.K1 === 'pending' );
},
/**
* Returns how many warnings the given function call metadata contains,
* counting the warnings raised by every nested function call too. The
* orchestrator does not necessarily merge the warnings of the nested
* calls into the parent metadata, so callers which must tell whether a
* function call raised any warning have to look at the whole tree.
*
* @param {Object} metadata a Z883/Typed map, in canonical form
* @return {number}
*/
countMetadataWarnings: function ( metadata ) {
if ( !metadata || !Array.isArray( metadata[ Constants.Z_TYPED_OBJECT_ELEMENT_1 ] ) ) {
return 0;
}
let count = extractWarningsData( getValueFromCanonicalZMap( metadata, 'warnings' ) ).length;
const nested = getValueFromCanonicalZMap( metadata, 'nestedMetadata' );
if ( Array.isArray( nested ) ) {
for ( const child of nested.slice( 1 ) ) {
count += zobjectUtils.countMetadataWarnings( child );
}
}
return count;
}
};
module.exports = zobjectUtils;
|