diff options
Diffstat (limited to 'extensions/WikiEditor/modules/jquery.wikiEditor.templateEditor.js')
-rw-r--r-- | extensions/WikiEditor/modules/jquery.wikiEditor.templateEditor.js | 865 |
1 files changed, 865 insertions, 0 deletions
diff --git a/extensions/WikiEditor/modules/jquery.wikiEditor.templateEditor.js b/extensions/WikiEditor/modules/jquery.wikiEditor.templateEditor.js new file mode 100644 index 00000000..794bddc7 --- /dev/null +++ b/extensions/WikiEditor/modules/jquery.wikiEditor.templateEditor.js @@ -0,0 +1,865 @@ +/* TemplateEditor module for wikiEditor */ +( function( $ ) { $.wikiEditor.modules.templateEditor = { +/** + * Name mappings, dirty hack which will be removed once "TemplateInfo" extension is more fully supported + */ +'nameMappings': { //keep these all lowercase to navigate web of redirects + "infobox skyscraper": "building_name", + "infobox settlement": "official_name" +}, + + +/** + * Compatability map + */ +'browsers': { + // Left-to-right languages + 'ltr': { + 'msie': [['>=', 8]], + 'firefox': [['>=', 3]], + 'opera': [['>=', 10]], + 'safari': [['>=', 4]] + }, + // Right-to-left languages + 'rtl': { + 'msie': false, + 'firefox': [['>=', 3]], + 'opera': [['>=', 10]], + 'safari': [['>=', 4]] + } +}, +/** + * Core Requirements + */ +'req': [ 'iframe' ], +/** + * Event handlers + */ +evt: { + + mark: function( context, event ) { + // The markers returned by this function are skipped on realchange, so don't regenerate them in that case + if ( context.modules.highlight.currentScope == 'realchange' ) { + return; + } + + // Get references to the markers and tokens from the current context + var markers = context.modules.highlight.markers; + var tokenArray = context.modules.highlight.tokenArray; + // Collect matching level 0 template call boundaries from the tokenArray + var level = 0; + var tokenIndex = 0; + while ( tokenIndex < tokenArray.length ){ + while ( tokenIndex < tokenArray.length && tokenArray[tokenIndex].label != 'TEMPLATE_BEGIN' ) { + tokenIndex++; + } + //open template + if ( tokenIndex < tokenArray.length ) { + var beginIndex = tokenIndex; + var endIndex = -1; //no match found + var openTemplates = 1; + var templatesMatched = false; + while ( tokenIndex < tokenArray.length - 1 && endIndex == -1 ) { + tokenIndex++; + if ( tokenArray[tokenIndex].label == 'TEMPLATE_BEGIN' ) { + openTemplates++; + } else if ( tokenArray[tokenIndex].label == 'TEMPLATE_END' ) { + openTemplates--; + if ( openTemplates == 0 ) { + endIndex = tokenIndex; + } //we can stop looping + } + }//while finding template ending + if ( endIndex != -1 ) { + markers.push( { + start: tokenArray[beginIndex].offset, + end: tokenArray[endIndex].offset, + type: 'template', + anchor: 'wrap', + afterWrap: function( node ) { + // Generate model + var model = $.wikiEditor.modules.templateEditor.fn.updateModel( $( node ) ); + if ( model.isCollapsible() ) { + $.wikiEditor.modules.templateEditor.fn.wrapTemplate( $( node ) ); + $.wikiEditor.modules.templateEditor.fn.bindTemplateEvents( $( node ) ); + } else { + $( node ).addClass( 'wikiEditor-template-text' ); + } + }, + beforeUnwrap: function( node ) { + if ( $( node ).parent().hasClass( 'wikiEditor-template' ) ) { + $.wikiEditor.modules.templateEditor.fn.unwrapTemplate( $( node ) ); + } + }, + onSkip: function( node ) { + if ( $( node ).html() == $( node ).data( 'oldHTML' ) ) { + // No change + return; + } + + // Text changed, regenerate model + var model = $.wikiEditor.modules.templateEditor.fn.updateModel( $( node ) ); + + // Update template name if needed + if ( $( node ).parent().hasClass( 'wikiEditor-template' ) ) { + var $label = $( node ).parent().find( '.wikiEditor-template-label' ); + var displayName = $.wikiEditor.modules.templateEditor.fn.getTemplateDisplayName( model ); + if ( $label.text() != displayName ) { + $label.text( displayName ); + } + } + + // Wrap or unwrap the template if needed + if ( $( node ).parent().hasClass( 'wikiEditor-template' ) && + !model.isCollapsible() ) { + $.wikiEditor.modules.templateEditor.fn.unwrapTemplate( $( node ) ); + } else if ( !$( node ).parent().hasClass( 'wikiEditor-template' ) && + model.isCollapsible() ) { + $.wikiEditor.modules.templateEditor.fn.wrapTemplate( $( node ) ); + $.wikiEditor.modules.templateEditor.fn.bindTemplateEvents( $( node ) ); + } + }, + getAnchor: function( ca1, ca2 ) { + return $( ca1.parentNode ).is( 'span.wikiEditor-template-text' ) ? + ca1.parentNode : null; + }, + context: context, + skipDivision: 'realchange' + } ); + } else { //else this was an unmatched opening + tokenArray[beginIndex].label = 'TEMPLATE_FALSE_BEGIN'; + tokenIndex = beginIndex; + } + }//if opentemplates + } + }, //mark + + keydown: function( context, event ) { + // Reset our ignoreKeypress variable if it's set to true + if ( context.$iframe.data( 'ignoreKeypress' ) ) { + context.$iframe.data( 'ignoreKeypress', false ); + } + var $evtElem = event.jQueryNode; + if ( $evtElem.hasClass( 'wikiEditor-template-label' ) ) { + // Allow anything if the command or control key are depressed + if ( event.ctrlKey || event.metaKey ) return true; + switch ( event.which ) { + case 13: // Enter + $evtElem.click(); + event.preventDefault(); + return false; + case 32: // Space + $evtElem.parent().siblings( '.wikiEditor-template-expand' ).click(); + event.preventDefault(); + return false; + case 37:// Left + case 38:// Up + case 39:// Right + case 40: //Down + return true; + default: + // Set the ignroreKeypress variable so we don't allow typing if the key is held + context.$iframe.data( 'ignoreKeypress', true ); + // Can't type in a template name + event.preventDefault(); + return false; + } + } else if ( $evtElem.hasClass( 'wikiEditor-template-text' ) ) { + switch ( event.which ) { + case 13: // Enter + // Ensure that the user can't break this by holding in the enter key + context.$iframe.data( 'ignoreKeypress', true ); + // FIXME: May be a more elegant way to do this, but this works too + context.fn.encapsulateSelection( { 'pre': '\n', 'peri': '', 'post': '' } ); + event.preventDefault(); + return false; + default: return true; + } + } + }, + keyup: function( context, event ) { + // Rest our ignoreKeypress variable if it's set to true + if ( context.$iframe.data( 'ignoreKeypress' ) ) { + context.$iframe.data( 'ignoreKeypress', false ); + } + return true; + }, + keypress: function( context, event ) { + // If this event is from a keydown event which we want to block, ignore it + return ( context.$iframe.data( 'ignoreKeypress' ) ? false : true ); + } +}, +/** + * Regular expressions that produce tokens + */ +exp: [ + { 'regex': /{{/, 'label': "TEMPLATE_BEGIN" }, + { 'regex': /}}/, 'label': "TEMPLATE_END", 'markAfter': true } +], +/** + * Configuration + */ +cfg: { +}, +/** + * Internally used functions + */ +fn: { + /** + * Creates template form module within wikieditor + * @param context Context object of editor to create module in + * @param config Configuration object to create module from + */ + create: function( context, config ) { + // Initialize module within the context + context.modules.templateEditor = {}; + }, + /** + * Turns a simple template wrapper (really just a <span>) into a complex one + * @param $wrapper Wrapping <span> + */ + wrapTemplate: function( $wrapper ) { + var model = $wrapper.data( 'model' ); + var context = $wrapper.data( 'marker' ).context; + var $template = $wrapper + .wrap( '<span class="wikiEditor-template"></span>' ) + .addClass( 'wikiEditor-template-text wikiEditor-template-text-shrunken' ) + .parent() + .addClass( 'wikiEditor-template-collapsed' ) + .prepend( + '<span class="wikiEditor-template-expand wikiEditor-noinclude"></span>' + + '<span class="wikiEditor-template-name wikiEditor-noinclude">' + + '<span class="wikiEditor-template-label wikiEditor-noinclude">' + + $.wikiEditor.modules.templateEditor.fn.getTemplateDisplayName( model ) + '</span>' + + '<span class="wikiEditor-template-dialog wikiEditor-noinclude"></span>' + + '</span>' + ); + }, + /** + * Turn a complex template wrapper back into a simple one + * @param $wrapper Wrapping <span> + */ + unwrapTemplate: function( $wrapper ) { + $wrapper.parent().replaceWith( $wrapper ); + }, + /** + * Bind events to a template + * @param $wrapper Original wrapper for the template to bind events to + */ + bindTemplateEvents: function( $wrapper ) { + var $template = $wrapper.parent( '.wikiEditor-template' ); + + if ( typeof ( opera ) == "undefined" ) { + $template.parent().attr('contentEditable', 'false'); + } + + $template.click( function(event) {event.preventDefault(); return false;} ); + + $template.find( '.wikiEditor-template-name' ) + .click( function( event ) { + $.wikiEditor.modules.templateEditor.fn.createDialog( $wrapper ); + event.stopPropagation(); + return false; + } ) + .mousedown( function( event ) { event.stopPropagation(); return false; } ); + $template.find( '.wikiEditor-template-expand' ) + .click( function( event ) { + $.wikiEditor.modules.templateEditor.fn.toggleWikiTextEditor( $wrapper ); + event.stopPropagation(); + return false; + } ) + .mousedown( function( event ) { event.stopPropagation(); return false; } ); + }, + /** + * Toggle the visisbilty of the wikitext for a given template + * @param $wrapper The origianl wrapper we want expand/collapse + */ + toggleWikiTextEditor: function( $wrapper ) { + var context = $wrapper.data( 'marker' ).context; + var $template = $wrapper.parent( '.wikiEditor-template' ); + context.fn.purgeOffsets(); + $template + .toggleClass( 'wikiEditor-template-expanded' ) + .toggleClass( 'wikiEditor-template-collapsed' ) ; + + var $templateText = $template.find( '.wikiEditor-template-text' ); + $templateText.toggleClass( 'wikiEditor-template-text-shrunken' ); + $templateText.toggleClass( 'wikiEditor-template-text-visible' ); + if( $templateText.hasClass('wikiEditor-template-text-shrunken') ){ + //we just closed the template + + // Update the model if we need to + if ( $templateText.html() != $templateText.data( 'oldHTML' ) ) { + var templateModel = $.wikiEditor.modules.templateEditor.fn.updateModel( $templateText ); + + //this is the only place the template name can be changed; keep the template name in sync + var $tLabel = $template.find( '.wikiEditor-template-label' ); + $tLabel.text( $.wikiEditor.modules.templateEditor.fn.getTemplateDisplayName( templateModel ) ); + } + + } + }, + /** + * Create a dialog for editing a given template and open it + * @param $wrapper The origianl wrapper for which to create the dialog + */ + createDialog: function( $wrapper ) { + var context = $wrapper.data( 'marker' ).context; + var $template = $wrapper.parent( '.wikiEditor-template' ); + var dialog = { + 'titleMsg': 'wikieditor-template-editor-dialog-title', + 'id': 'wikiEditor-template-dialog', + 'html': '\ + <fieldset>\ + <div class="wikiEditor-template-dialog-title" />\ + <div class="wikiEditor-template-dialog-fields" />\ + </fieldset>', + init: function() { + $(this).find( '[rel]' ).each( function() { + $(this).text( mediaWiki.msg( $(this).attr( 'rel' ) ) ); + } ); + }, + immediateCreate: true, + dialog: { + width: 600, + height: 400, + dialogClass: 'wikiEditor-toolbar-dialog', + buttons: { + 'wikieditor-template-editor-dialog-submit': function() { + // More user feedback + var $templateDiv = $( this ).data( 'templateDiv' ); + context.fn.highlightLine( $templateDiv ); + + var $templateText = $templateDiv.children( '.wikiEditor-template-text' ); + var templateModel = $templateText.data( 'model' ); + $( this ).find( '.wikiEditor-template-dialog-field-wrapper textarea' ).each( function() { + // Update the value + templateModel.setValue( $( this ).data( 'name' ), $( this ).val() ); + }); + //keep text consistent + $.wikiEditor.modules.templateEditor.fn.updateModel( $templateText, templateModel ); + + $( this ).dialog( 'close' ); + }, + 'wikieditor-template-editor-dialog-cancel': function() { + $(this).dialog( 'close' ); + } + }, + open: function() { + var $templateDiv = $( this ).data( 'templateDiv' ); + var $templateText = $templateDiv.children( '.wikiEditor-template-text' ); + var templateModel = $templateText.data( 'model' ); + // Update the model if we need to + if ( $templateText.html() != $templateText.data( 'oldHTML' ) ) { + templateModel = $.wikiEditor.modules.templateEditor.fn.updateModel( $templateText ); + } + + // Build the table + // TODO: Be smart and recycle existing table + var params = templateModel.getAllInitialParams(); + var $fields = $( this ).find( '.wikiEditor-template-dialog-fields' ); + // Do some bookkeeping so we can recycle existing rows + var $rows = $fields.find( '.wikiEditor-template-dialog-field-wrapper' ); + for ( var paramIndex in params ) { + var param = params[paramIndex]; + if ( typeof param.name == 'undefined' ) { + // param is the template name, skip it + continue; + } + var paramText = typeof param == 'string' ? + param.name.replace( /[\_\-]/g, ' ' ) : + param.name; + var paramVal = templateModel.getValue( param.name ); + if ( $rows.length > 0 ) { + // We have another row to recycle + var $row = $rows.eq( 0 ); + $row.children( 'label' ).text( paramText ); + $row.children( 'textarea' ) + .data( 'name', param.name ) + .val( paramVal ) + .each( function() { + $(this).css( 'height', $(this).val().length > 24 ? '4.5em' : '1.5em' ); + } ); + $rows = $rows.not( $row ); + } else { + // Create a new row + var $paramRow = $( '<div />' ) + .addClass( 'wikiEditor-template-dialog-field-wrapper' ); + $( '<label />' ) + .text( paramText ) + .appendTo( $paramRow ); + $( '<textarea />' ) + .data( 'name', param.name ) + .val( paramVal ) + .each( function() { + $(this).css( 'height', $(this).val().length > 24 ? '4.5em' : '1.5em' ); + } ) + .data( 'expanded', false ) + .bind( 'cut paste keypress click change', function( e ) { + // If this was fired by a tab keypress, let it go + if ( e.keyCode == '9' ) return true; + var $this = $( this ); + setTimeout( function() { + var expanded = $this.data( 'expanded' ); + if ( $this.val().indexOf( '\n' ) != -1 || $this.val().length > 24 ) { + if ( !expanded ) { + $this.animate( { 'height': '4.5em' }, 'fast' ); + $this.data( 'expanded', true ); + } + } else { + if ( expanded ) { + $this.animate( { 'height': '1.5em' }, 'fast' ); + $this.data( 'expanded', false ); + } + } + }, 0 ); + } ) + .appendTo( $paramRow ); + $paramRow + .append( '<div style="clear:both"></div>' ) + .appendTo( $fields ); + } + } + + // Remove any leftover rows + $rows.remove(); + $fields.find( 'label' ).autoEllipsis(); + // Ensure our close button doesn't recieve the ui-state-focus class + $( this ).parent( '.ui-dialog' ).find( '.ui-dialog-titlebar-close' ) + .removeClass( 'ui-state-focus' ); + + // Set tabindexes on form fields if needed + // First unset the tabindexes on the buttons and existing form fields + // so the order doesn't get messed up + var $needTabindex = $( this ).closest( '.ui-dialog' ).find( 'button, textarea' ); + if ( $needTabindex.not( '[tabindex]' ).length ) { + // Only do this if there actually are elements missing a tabindex + $needTabindex.removeAttr( 'tabindex' ); + $.wikiEditor.modules.dialogs.fn.setTabindexes( $needTabindex ); + } + } + } + }; + // Lazy-create the dialog at this time + context.$textarea.wikiEditor( 'addDialog', { 'templateEditor': dialog } ); + $( '#' + dialog.id ) + .data( 'templateDiv', $template ) + .dialog( 'open' ); + }, + /** + * Update a template's model and HTML + * @param $templateText Wrapper <span> containing the template text + * @param model Template model to use, will be generated if not set + * @return model object + */ + updateModel: function( $templateText, model ) { + var context = $templateText.data( 'marker' ).context; + var text; + if ( typeof model == 'undefined' ) { + text = context.fn.htmlToText( $templateText.html() ); + } else { + text = model.getText(); + } + // To keep stuff simple but not break it, we need to do encode newlines as <br>s + $templateText.text( text ); + $templateText.html( $templateText.html().replace( /\n/g, '<br />' ) ); + $templateText.data( 'oldHTML', $templateText.html() ); + if ( typeof model == 'undefined' ) { + model = new $.wikiEditor.modules.templateEditor.fn.model( text ); + $templateText.data( 'model', model ); + } + return model; + }, + + /** + * Gets template display name + */ + getTemplateDisplayName: function ( model ) { + var tName = model.getName(); + if( model.getValue( 'name' ) != '' ) { + return tName + ': ' + model.getValue( 'name' ); + } else if( model.getValue( 'Name' ) != '' ) { + return tName + ': ' + model.getValue( 'Name' ); + } else if( tName.toLowerCase() in $.wikiEditor.modules.templateEditor.nameMappings ) { + return tName + ': ' + model.getValue( $.wikiEditor.modules.templateEditor.nameMappings[tName.toLowerCase()] ); + } + return tName; + }, + + /** + * Builds a template model from given wikitext representation, allowing object-oriented manipulation of the contents + * of the template while preserving whitespace and formatting. + * + * @param wikitext String of wikitext content + */ + model: function( wikitext ) { + + /* Private members */ + + var collapsible = true; + + /* Private Functions */ + + /** + * Builds a Param object. + * + * @param name + * @param value + * @param number + * @param nameIndex + * @param equalsIndex + * @param valueIndex + */ + function Param( name, value, number, nameIndex, equalsIndex, valueIndex ) { + this.name = name; + this.value = value; + this.number = number; + this.nameIndex = nameIndex; + this.equalsIndex = equalsIndex; + this.valueIndex = valueIndex; + } + /** + * Builds a Range object. + * + * @param begin + * @param end + */ + function Range( begin, end ) { + this.begin = begin; + this.end = end; + } + /** + * Set 'original' to true if you want the original value irrespective of whether the model's been changed + * + * @param name + * @param value + * @param original + */ + function getSetValue( name, value, original ) { + var valueRange; + var rangeIndex; + var retVal; + if ( isNaN( name ) ) { + // It's a string! + if ( typeof paramsByName[name] == 'undefined' ) { + // Does not exist + return ""; + } + rangeIndex = paramsByName[name]; + } else { + // It's a number! + rangeIndex = parseInt( name ); + } + if ( typeof params[rangeIndex] == 'undefined' ) { + // Does not exist + return ""; + } + valueRange = ranges[params[rangeIndex].valueIndex]; + if ( typeof valueRange.newVal == 'undefined' || original ) { + // Value unchanged, return original wikitext + retVal = wikitext.substring( valueRange.begin, valueRange.end ); + } else { + // New value exists, return new value + retVal = valueRange.newVal; + } + if ( value != null ) { + ranges[params[rangeIndex].valueIndex].newVal = value; + } + return retVal; + }; + + /* Public Functions */ + + /** + * Get template name + */ + this.getName = function() { + if( typeof ranges[templateNameIndex].newVal == 'undefined' ) { + return wikitext.substring( ranges[templateNameIndex].begin, ranges[templateNameIndex].end ); + } else { + return ranges[templateNameIndex].newVal; + } + }; + /** + * Set template name (if we want to support this) + * + * @param name + */ + this.setName = function( name ) { + ranges[templateNameIndex].newVal = name; + }; + /** + * Set value for a given param name / number + * + * @param name + * @param value + */ + this.setValue = function( name, value ) { + return getSetValue( name, value, false ); + }; + /** + * Get value for a given param name / number + * + * @param name + */ + this.getValue = function( name ) { + return getSetValue( name, null, false ); + }; + /** + * Get original value of a param + * + * @param name + */ + this.getOriginalValue = function( name ) { + return getSetValue( name, null, true ); + }; + /** + * Get a list of all param names (numbers for the anonymous ones) + */ + this.getAllParamNames = function() { + return paramsByName; + }; + /** + * Get the initial params + */ + this.getAllInitialParams = function(){ + return params; + }; + /** + * Get original template text + */ + this.getOriginalText = function() { + return wikitext; + }; + /** + * Get modified template text + */ + this.getText = function() { + newText = ""; + for ( i = 0 ; i < ranges.length; i++ ) { + if( typeof ranges[i].newVal == 'undefined' ) { + newText += wikitext.substring( ranges[i].begin, ranges[i].end ); + } else { + newText += ranges[i].newVal; + } + } + return newText; + }; + + this.isCollapsible = function() { + return collapsible; + }; + + /** + * Update ranges if there's been a change in one or more 'segments' of the template. + * Removes adjustment function so adjustment is only made once ever. + */ + + this.updateRanges = function() { + var adjustment = 0; + for (var i = 0 ; i < ranges.length; i++ ) { + ranges[i].begin += adjustment; + if( typeof ranges[i].adjust != 'undefined' ) { + adjustment += ranges[i].adjust(); + // NOTE: adjust should be a function that has the information necessary to calculate the length of + // this 'segment' + delete ranges[i].adjust; + } + ranges[i].end += adjustment; + } + }; + + // Whitespace* {{ whitespace* nonwhitespace: + if ( wikitext.match( /\s*{{\s*[^\s|]*:/ ) ) { + collapsible = false; // is a parser function + } + /* + * Take all template-specific characters that are not particular to the template we're looking at, namely {|=}, + * and convert them into something harmless, in this case 'X' + */ + // Get rid of first {{ with whitespace + var sanatizedStr = wikitext.replace( /{{/, " " ); + // Replace end + endBraces = sanatizedStr.match( /}}\s*$/ ); + if ( endBraces ) { + sanatizedStr = sanatizedStr.substring( 0, endBraces.index ) + " " + + sanatizedStr.substring( endBraces.index + 2 ); + } + + + //treat HTML comments like whitespace + while ( sanatizedStr.indexOf( '<!' ) != -1 ) { + startIndex = sanatizedStr.indexOf( '<!' ); + endIndex = sanatizedStr.indexOf('-->') + 3; + if( endIndex < 3 ){ + break; + } + sanatizedSegment = sanatizedStr.substring( startIndex,endIndex ).replace( /\S/g , ' ' ); + sanatizedStr = + sanatizedStr.substring( 0, startIndex ) + sanatizedSegment + sanatizedStr.substring( endIndex ); + } + + // Match the open braces we just found with equivalent closing braces note, works for any level of braces + while ( sanatizedStr.indexOf( '{{' ) != -1 ) { + startIndex = sanatizedStr.indexOf( '{{' ) + 1; + openBraces = 2; + endIndex = startIndex; + while ( (openBraces > 0) && (endIndex < sanatizedStr.length) ) { + var brace = sanatizedStr[++endIndex]; + openBraces += brace == '}' ? -1 : brace == '{' ? 1 : 0; + } + sanatizedSegment = sanatizedStr.substring( startIndex,endIndex ).replace( /[{}|=]/g , 'X' ); + sanatizedStr = + sanatizedStr.substring( 0, startIndex ) + sanatizedSegment + sanatizedStr.substring( endIndex ); + } + //links, images, etc, which also can nest + while ( sanatizedStr.indexOf( '[[' ) != -1 ) { + startIndex = sanatizedStr.indexOf( '[[' ) + 1; + openBraces = 2; + endIndex = startIndex; + while ( (openBraces > 0) && (endIndex < sanatizedStr.length) ) { + var brace = sanatizedStr[++endIndex]; + openBraces += brace == ']' ? -1 : brace == '[' ? 1 : 0; + } + sanatizedSegment = sanatizedStr.substring( startIndex,endIndex ).replace( /[\[\]|=]/g , 'X' ); + sanatizedStr = + sanatizedStr.substring( 0, startIndex ) + sanatizedSegment + sanatizedStr.substring( endIndex ); + } + + /* + * Parse 1 param at a time + */ + var ranges = []; + var params = []; + var templateNameIndex = 0; + var doneParsing = false; + oldDivider = 0; + divider = sanatizedStr.indexOf( '|', oldDivider ); + if ( divider == -1 ) { + divider = sanatizedStr.length; + doneParsing = true; + collapsible = false; //zero params + } + nameMatch = sanatizedStr.substring( 0, divider ).match( /[^\s]/ ); + if ( nameMatch != null ) { + ranges.push( new Range( 0 ,nameMatch.index ) ); //whitespace and squiggles upto the name + nameEndMatch = sanatizedStr.substring( 0 , divider ).match( /[^\s]\s*$/ ); //last nonwhitespace character + templateNameIndex = ranges.push( new Range( nameMatch.index, + nameEndMatch.index + 1 ) ); + templateNameIndex--; //push returns 1 less than the array + ranges[templateNameIndex].old = wikitext.substring( ranges[templateNameIndex].begin, + ranges[templateNameIndex].end ); + } else { + ranges.push(new Range(0,0)); + ranges[templateNameIndex].old = ""; + } + params.push( ranges[templateNameIndex].old ); //put something in params (0) + /* + * Start looping over params + */ + var currentParamNumber = 0; + var valueEndIndex = ranges[templateNameIndex].end; + var paramsByName = []; + while ( !doneParsing ) { + currentParamNumber++; + oldDivider = divider; + divider = sanatizedStr.indexOf( '|', oldDivider + 1 ); + if ( divider == -1 ) { + divider = sanatizedStr.length; + doneParsing = true; + } + currentField = sanatizedStr.substring( oldDivider+1, divider ); + if ( currentField.indexOf( '=' ) == -1 ) { + // anonymous field, gets a number + + //default values, since we'll allow empty values + valueBeginIndex = oldDivider + 1; + valueEndIndex = oldDivider + 1; + + valueBegin = currentField.match( /\S+/ ); //first nonwhitespace character + if( valueBegin != null ){ + valueBeginIndex = valueBegin.index + oldDivider+1; + valueEnd = currentField.match( /[^\s]\s*$/ ); //last nonwhitespace character + if( valueEnd == null ){ //ie + continue; + } + valueEndIndex = valueEnd.index + oldDivider + 2; + } + ranges.push( new Range( ranges[ranges.length-1].end, + valueBeginIndex ) ); //all the chars upto now + nameIndex = ranges.push( new Range( valueBeginIndex, valueBeginIndex ) ) - 1; + equalsIndex = ranges.push( new Range( valueBeginIndex, valueBeginIndex ) ) - 1; + valueIndex = ranges.push( new Range( valueBeginIndex, valueEndIndex ) ) - 1; + params.push( new Param( + currentParamNumber, + wikitext.substring( ranges[valueIndex].begin, ranges[valueIndex].end ), + currentParamNumber, + nameIndex, + equalsIndex, + valueIndex + ) ); + paramsByName[currentParamNumber] = currentParamNumber; + } else { + // There's an equals, could be comment or a value pair + currentName = currentField.substring( 0, currentField.indexOf( '=' ) ); + // Still offset by oldDivider - first nonwhitespace character + nameBegin = currentName.match( /\S+/ ); + if ( nameBegin == null ) { + // This is a comment inside a template call / parser abuse. let's not encourage it + currentParamNumber--; + continue; + } + nameBeginIndex = nameBegin.index + oldDivider + 1; + // Last nonwhitespace and non } character + nameEnd = currentName.match( /[^\s]\s*$/ ); + if( nameEnd == null ){ //ie + continue; + } + nameEndIndex = nameEnd.index + oldDivider + 2; + // All the chars upto now + ranges.push( new Range( ranges[ranges.length-1].end, nameBeginIndex ) ); + nameIndex = ranges.push( new Range( nameBeginIndex, nameEndIndex ) ) - 1; + currentValue = currentField.substring( currentField.indexOf( '=' ) + 1); + oldDivider += currentField.indexOf( '=' ) + 1; + + //default values, since we'll allow empty values + valueBeginIndex = oldDivider + 1; + valueEndIndex = oldDivider + 1; + + // First nonwhitespace character + valueBegin = currentValue.match( /\S+/ ); + if( valueBegin != null ){ + valueBeginIndex = valueBegin.index + oldDivider + 1; + // Last nonwhitespace and non } character + valueEnd = currentValue.match( /[^\s]\s*$/ ); + if( valueEnd == null ){ //ie + continue; + } + valueEndIndex = valueEnd.index + oldDivider + 2; + } + // All the chars upto now + equalsIndex = ranges.push( new Range( ranges[ranges.length-1].end, valueBeginIndex) ) - 1; + valueIndex = ranges.push( new Range( valueBeginIndex, valueEndIndex ) ) - 1; + params.push( new Param( + wikitext.substring( nameBeginIndex, nameEndIndex ), + wikitext.substring( valueBeginIndex, valueEndIndex ), + currentParamNumber, + nameIndex, + equalsIndex, + valueIndex + ) ); + paramsByName[wikitext.substring( nameBeginIndex, nameEndIndex )] = currentParamNumber; + } + } + // The rest of the string + ranges.push( new Range( valueEndIndex, wikitext.length ) ); + + // Save vars + this.ranges = ranges; + this.wikitext = wikitext; + this.params = params; + this.paramsByName = paramsByName; + this.templateNameIndex = templateNameIndex; + } //model +} +}; } )( jQuery ); |