All files / ext.wikilambda.edit/mixins zobjectUtils.js

100% Statements 48/48
100% Branches 47/47
100% Functions 6/6
100% Lines 47/47

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                        22x 22x 22x   22x                                             311x 1x       310x 1x     309x 309x     6808x     3742x 3742x           3066x   3066x       108x 108x   2958x 2958x 2958x       3066x 6499x     6499x           309x     309x               309x 20x     309x                           234815x 2059x     2059x 3x       2056x 237x     2056x 4514x   236x 236x 4278x   1487x 437x 1050x 1x   1049x       2791x       2056x   336x        
/*!
 * WikiLambda Vue ZObject manipulation utilities. This mixin contains
 * the methods for transforming a ZObject from its JSON representation
 * into a table representation that can be stored in the global state,
 * and back from table to JSON.
 *
 * For more details on the ZObject table read:
 * https://www.mediawiki.org/wiki/Extension:WikiLambda/Frontend_Architecture#ZObject_Table
 *
 * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt
 * @license MIT
 */
const Constants = require( '../Constants.js' ),
	Row = require( '../store/classes/Row.js' ),
	canonicalToHybrid = require( './schemata.js' ).methods.canonicalToHybrid;
 
module.exports = exports = {
	methods: {
		/**
		 * Convert a ZObject represented as a JS object into a flat array of
		 * rows, where each row represents a key-value. Every row has an ID,
		 * a key, a value and the row ID of its parent. Depending on the input
		 * parameters, the generated array of rows will be adapted to pend from
		 * a given parent row, or will be the whole ZObject to represent as the
		 * global state.
		 *
		 * @param {Object} zObject
		 * @param {Object} parentRow the row object from which the resulting object will pend
		 * @param {number} startingRowId the first available rowID in the global state table
		 * @param {boolean} appendToList whether to append item into parent list
		 * @param {number} appendFromIndex in the case of lists, specify the index from which
		 *        to append items, else will start from 0
		 * @param {boolean} returnParent whether to return the parent row in the row array
		 * @return {Array}
		 */
		convertJsonToTable: function (
			zObject, parentRow, startingRowId, appendToList = false, appendFromIndex = 0, returnParent = true ) {
 
			// Raise an exception if parentRow is set and nextAvailableId is not to avoid overwriting IDs
			if ( parentRow && !startingRowId ) {
				throw new Error( 'The parameter startingRowId must be set when inserting a ZObject under a parentRow' );
			}
 
			// Raise an exception if appendToList is set and parentRow is not
			if ( !parentRow && appendToList ) {
				throw new Error( 'It is only possible to append to list when inserting a ZObject under a parentRow' );
			}
 
			const zObjectRows = [];
			let nextAvailableId = startingRowId || 0;
 
			function flattenZObject( value, key, parentRowId, isExistingParent = false, startingIndex = 0 ) {
				if ( typeof value === 'string' ) {
					// ROW IS TERMINAL
					// Push a new row with its final value as 'value'
					zObjectRows.push( new Row( nextAvailableId, key, value, parentRowId ) );
					nextAvailableId++;
				} else {
					// ROW IS NOT TERMINAL
					// Push a non-terminal row with value set to either array or object
					// We don't push the parent if it's already a row in the zObjectTable
					let rowId;
					const type = Array.isArray( value ) ? Constants.ROW_VALUE_ARRAY : Constants.ROW_VALUE_OBJECT;
 
					if ( isExistingParent ) {
						// We are inserting the already existing parent with its own id,
						// key and parent; the only thing that may change is the value.
						// The calling method will have to decide whether to insert it or replace it.
						rowId = parentRow.id;
						zObjectRows.push( new Row( rowId, key, type, parentRow.parent ) );
					} else {
						rowId = nextAvailableId;
						zObjectRows.push( new Row( rowId, key, type, parentRowId ) );
						nextAvailableId++;
					}
 
					// And for every child, recurse with current rowId as parentRowId
					for ( const objectKey in value ) {
						const rowKey = ( type === Constants.ROW_VALUE_ARRAY ) ?
							String( parseInt( objectKey ) + startingIndex ) :
							objectKey;
						flattenZObject( value[ objectKey ], rowKey, rowId );
					}
				}
			}
 
			// If we are to append the value to a parent list, wrap value in Array
			const childValue = appendToList ? [ zObject ] : zObject;
 
			// Initial call, if there's a parent, link with key and parent id, else undefined
			flattenZObject(
				canonicalToHybrid( childValue ),
				parentRow ? parentRow.key : undefined,
				parentRow ? parentRow.id : undefined,
				!!parentRow,
				appendFromIndex
			);
 
			if ( !returnParent ) {
				zObjectRows.shift();
			}
 
			return zObjectRows;
		},
 
		/**
		 * Converts the zObject flattened table into a nested object starting
		 * from a given rowId
		 *
		 * @param {Array} zObjectTable array of Row objects
		 * @param {number} parentId starting rowId
		 * @param {boolean} rootIsArray
		 * @return {Object}
		 */
		convertTableToJson: function ( zObjectTable, parentId = 0, rootIsArray = false ) {
			function reconstructJson( table, rowId, isArrayChild ) {
				const rows = table.filter( ( item ) => item.parent === rowId );
				let json = {},
					value;
 
				if ( rows.length === 0 && !isArrayChild ) {
					return;
				}
 
				// if array children, we need to return an array not an object
				if ( isArrayChild ) {
					json = [];
				}
 
				rows.forEach( ( row ) => {
					if ( row.isArray() ) {
						// row is parent of array
						value = reconstructJson( table, row.id, true );
						json[ row.key ] = value;
					} else if ( row.isObject() ) {
						// row is parent of object
						if ( isArrayChild ) {
							json[ row.key ] = reconstructJson( table, row.id );
						} else if ( !row.key ) {
							json = reconstructJson( table, row.id );
						} else {
							json[ row.key ] = reconstructJson( table, row.id );
						}
					} else {
						// row is terminal
						json[ row.key ] = row.value;
					}
				} );
 
				return json;
			}
			return reconstructJson( zObjectTable, parentId, rootIsArray );
		}
	}
};