mirror of
				https://github.com/JKorf/CryptoExchange.Net
				synced 2025-10-31 02:17:45 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			136 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			136 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| export function escapeHTML(value) {
 | |
|   return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * performs a shallow merge of multiple objects into one
 | |
|  *
 | |
|  * @arguments list of objects with properties to merge
 | |
|  * @returns a single new object
 | |
|  */
 | |
| export function inherit(parent) { // inherit(parent, override_obj, override_obj, ...)
 | |
|   var result = {};
 | |
|   var objects = Array.prototype.slice.call(arguments, 1);
 | |
| 
 | |
|   for (const key in parent) {
 | |
|     result[key] = parent[key];
 | |
|   }
 | |
|   objects.forEach(function(obj) {
 | |
|     for (const key in obj) {
 | |
|       result[key] = obj[key];
 | |
|     }
 | |
|   });
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /* Stream merging */
 | |
| 
 | |
| function tag(node) {
 | |
|   return node.nodeName.toLowerCase();
 | |
| }
 | |
| 
 | |
| export function nodeStream(node) {
 | |
|   var result = [];
 | |
|   (function _nodeStream(node, offset) {
 | |
|     for (var child = node.firstChild; child; child = child.nextSibling) {
 | |
|       if (child.nodeType === 3) {
 | |
|         offset += child.nodeValue.length;
 | |
|       } else if (child.nodeType === 1) {
 | |
|         result.push({
 | |
|           event: 'start',
 | |
|           offset: offset,
 | |
|           node: child
 | |
|         });
 | |
|         offset = _nodeStream(child, offset);
 | |
|         // Prevent void elements from having an end tag that would actually
 | |
|         // double them in the output. There are more void elements in HTML
 | |
|         // but we list only those realistically expected in code display.
 | |
|         if (!tag(child).match(/br|hr|img|input/)) {
 | |
|           result.push({
 | |
|             event: 'stop',
 | |
|             offset: offset,
 | |
|             node: child
 | |
|           });
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     return offset;
 | |
|   })(node, 0);
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| export function mergeStreams(original, highlighted, value) {
 | |
|   var processed = 0;
 | |
|   var result = '';
 | |
|   var nodeStack = [];
 | |
| 
 | |
|   function selectStream() {
 | |
|     if (!original.length || !highlighted.length) {
 | |
|       return original.length ? original : highlighted;
 | |
|     }
 | |
|     if (original[0].offset !== highlighted[0].offset) {
 | |
|       return (original[0].offset < highlighted[0].offset) ? original : highlighted;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|     To avoid starting the stream just before it should stop the order is
 | |
|     ensured that original always starts first and closes last:
 | |
| 
 | |
|     if (event1 == 'start' && event2 == 'start')
 | |
|       return original;
 | |
|     if (event1 == 'start' && event2 == 'stop')
 | |
|       return highlighted;
 | |
|     if (event1 == 'stop' && event2 == 'start')
 | |
|       return original;
 | |
|     if (event1 == 'stop' && event2 == 'stop')
 | |
|       return highlighted;
 | |
| 
 | |
|     ... which is collapsed to:
 | |
|     */
 | |
|     return highlighted[0].event === 'start' ? original : highlighted;
 | |
|   }
 | |
| 
 | |
|   function open(node) {
 | |
|     function attr_str(a) {
 | |
|       return ' ' + a.nodeName + '="' + escapeHTML(a.value).replace(/"/g, '"') + '"';
 | |
|     }
 | |
|     result += '<' + tag(node) + [].map.call(node.attributes, attr_str).join('') + '>';
 | |
|   }
 | |
| 
 | |
|   function close(node) {
 | |
|     result += '</' + tag(node) + '>';
 | |
|   }
 | |
| 
 | |
|   function render(event) {
 | |
|     (event.event === 'start' ? open : close)(event.node);
 | |
|   }
 | |
| 
 | |
|   while (original.length || highlighted.length) {
 | |
|     var stream = selectStream();
 | |
|     result += escapeHTML(value.substring(processed, stream[0].offset));
 | |
|     processed = stream[0].offset;
 | |
|     if (stream === original) {
 | |
|       /*
 | |
|       On any opening or closing tag of the original markup we first close
 | |
|       the entire highlighted node stack, then render the original tag along
 | |
|       with all the following original tags at the same offset and then
 | |
|       reopen all the tags on the highlighted stack.
 | |
|       */
 | |
|       nodeStack.reverse().forEach(close);
 | |
|       do {
 | |
|         render(stream.splice(0, 1)[0]);
 | |
|         stream = selectStream();
 | |
|       } while (stream === original && stream.length && stream[0].offset === processed);
 | |
|       nodeStack.reverse().forEach(open);
 | |
|     } else {
 | |
|       if (stream[0].event === 'start') {
 | |
|         nodeStack.push(stream[0].node);
 | |
|       } else {
 | |
|         nodeStack.pop();
 | |
|       }
 | |
|       render(stream.splice(0, 1)[0]);
 | |
|     }
 | |
|   }
 | |
|   return result + escapeHTML(value.substr(processed));
 | |
| }
 |