/* The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is comprised of the ADF directory The Initial Developer of the Original Code is PaperThin, Inc. Copyright (c) 2009-2020. All Rights Reserved. By downloading, modifying, distributing, using and/or accessing any files in this directory, you agree to the terms and conditions of the applicable end user license agreement. */ /* *************************************************************** */ /* Author: PaperThin, Inc. Name: data_2_0.cfc Summary: Data Utils component functions for the ADF Library Version: 2.0 History: 2015-08-31 - GAC - Created 2016-12-05 - GAC - Added the capFirstAllWords() method 2017-01-27 - GAC - Added compareStructData() with subfunctions structCompare() and arrayCompare() 2019-07-01 - GAC - Added the padNumericValue method 2019-07-18 - GAC - Added the parseNumberWithUnits method 2019-12-02 - GAC - Added update version of trimStringByWordCount which uses tagStripper() to remove HTML tags before counting and trimming the string 2019-12-19 - GAC - Added filterInternationalChars (updated function name) and filterInternationlChars (for backwards compatibility) pass-through functions */ component displayname="data_2_0" extends="data_1_2" hint="Data Utils component functions for the ADF Library" output="no" { /* PROPERTIES */ property name="version" type="string" default="2_0_11"; property name="type" value="singleton"; property name="wikiTitle" value="Data_2_0"; /* Author: PaperThin, Inc. Name: $listDiffExtended Summary: Compares a old list to a new list and returns a struct of added, deleted, and matched values Returns: Struct added deleted matched Arguments: String - oldlist String - newlist String - delimiters History: 2016-08-26 - GAC - Created */ public struct function listDiffExtended(string oldlist="", string newlist="", string delimiters=",") { var listData = StructNew(); var i = 1; var newItem = ""; listData.added = ""; listData.deleted = ""; listData.matched = ""; // Loop over NewList to get deleted items (no need to get matched items) for (i=1; i LTE ListLen(arguments.newlist,arguments.delimiters); i=i+1) { newItem = ListGetAt(arguments.newlist, i,arguments.delimiters); // Check Old List for new items if ( ListFindNoCase(arguments.oldlist, newItem , arguments.delimiters) ) { // the NewItem already Exists... so add it to the matched list if ( ListFindNoCase(listData.matched, newItem, arguments.delimiters) EQ 0 ) listData.matched = ListAppend(listData.matched, newItem, arguments.delimiters); } else { // the NewItem was NOT found... so add it to the added list if ( ListFindNoCase(listData.added, newItem, arguments.delimiters) EQ 0 ) listData.added = ListAppend(listData.added, newItem, arguments.delimiters); } } // Loop over OldList to get deleted items (no need to get matched items) for (i=1; i LTE ListLen(arguments.oldlist,arguments.delimiters); i=i+1) { oldItem = ListGetAt(arguments.oldlist, i,arguments.delimiters); // Check Old List for new items if ( ListFindNoCase(arguments.newlist, oldItem , arguments.delimiters) EQ 0 ) { // the OldItem was NOT found... so add it to the deleted list if ( ListFindNoCase(listData.deleted , oldItem, arguments.delimiters) EQ 0 ) listData.deleted = ListAppend(listData.deleted , oldItem, arguments.delimiters); } } return listData; } /* Author: PaperThin, Inc. Name: $capFirstAllWords Summary: Updates a string to capitalize each word with options to skip specified words and/or to skip words wrapped in open and close punctuation marks eg. "no fix" or (no fix) Returns: String Arguments: String - str - string to capitalize each work String - skipWordsList - list of words to skip Boolean - preserveCaseOfWrappedWords - do not convert words that have been wrapped with punctuation History: 2016-11-22 - GAC - Created 2016-12-13 - GAC - Fixed issue with single character words 2017-11-09 - GAC - Fixed issue with the function removing ampersands from sentences */ public string function capFirstAllWords(string str="",string skipWordsList="", boolean preserveCaseOfWrappedWords=true) { var rtnStr = ""; var strPartsArr = ArrayNew(1); var strCapsArr = ArrayNew(1); var word = ""; var i = 0; var s = 0; var fixWord = false; var ampPlaceHolderStr = "[[-xAmp-]]"; var wordPrefix = ""; var wordSuffix = ""; // encode '&' chars to '&' arguments.str = encodeAmpersands(str=arguments.str,type="html"); // Create the word array strPartsArr = reMatch('([[:punct:]])?([[:word:]]+)([[:punct:]])?([[:word:]]+)?',arguments.str); for ( i=1; i LTE ArrayLen(strPartsArr); i=i+1){ fixWord = false; wordPrefix = ""; wordSuffix = ""; word = strPartsArr[i]; // Skip words if found in the skipWordsList (case-sensitive) if ( ListFind(arguments.skipWordsList,word) EQ 0 ) fixWord = true; // Find words surrounded by punctuation if ( REFind('[[:punct:]]', LEFT(word,1), 1) AND REFind('[[:punct:]]', RIGHT(word,1), 1) ) { // Skip words surrounded by punctuation if ( arguments.preserveCaseOfWrappedWords ) fixWord = false; else { wordPrefix = LEFT(word,1); wordSuffix = RIGHT(word,1); word = MID(word, 2, len(word)-2); } } if ( fixWord AND LEN(word) ) { word = lcase(word); if ( LEN(word) GT 1 ) word = uCase(left(word,1)) & right(word ,len(word)-1); else word = uCase(left(word,1)); if ( LEN(TRIM(wordPrefix)) AND LEN(TRIM(wordSuffix)) ) word = wordPrefix & word & wordSuffix; } // decode '&' back to '&' word = decodeAmpersands(str=word,type="html"); ArrayAppend(strCapsArr,word); } for ( s=1; s LTE ArrayLen(strCapsArr); s=s+1){ rtnStr = ListAppend(rtnStr,strCapsArr[s]," "); } return rtnStr; } /* Author: PaperThin, Inc. G. Cronkright Name: $compareStructData Summary: Compares two data structures and then returns a true if they are the same Returns: Boolean Arguments: Struct - structDataA Struct - structDataB String - excludeKeyList String - objectFieldKeyList - List of Keys whose values contain JSON object data History: 2017-01-03 - GAC - Created new version - GAC - Updated to duplicate the compare structs incase the excludeKey values are passed in - GAC - Updated to remove the unreliable .Equals() JAVA method and replaced it with the structCompare() subfunctions to better compare complex structs */ public boolean function compareStructData(required struct structDataA,required struct structDataB,string excludeKeyList="", string objectFieldKeyList="") { var isEqual = false; var i=1; var currentKey = ""; var dataAObjectValue = ""; var dataBObjectValue = ""; var dataA = duplicateStruct(arguments.structDataA); var dataB = duplicateStruct(arguments.structDataB); // Remove the exclude Keys before doing compare for ( i=1;i LTE ListLen(arguments.excludeKeyList);i=i+1 ) { currentKey = ListGetAt(arguments.excludeKeyList,i); if ( LEN(TRIM(currentKey)) ) { if ( StructKeyExists(dataA,currentKey) ) StructDelete(dataA,currentKey); if ( StructKeyExists(dataB,currentKey) ) StructDelete(dataB,currentKey); } } // Loop over the object fields to compare them individually for ( i=1;i LTE ListLen(arguments.objectFieldKeyList);i=i+1 ) { currentKey = ListGetAt(arguments.objectFieldKeyList,i); if ( LEN(TRIM(currentKey)) ) { // Compare the object fields if ( isJSON(dataA[currentKey]) AND isJSON(dataB[currentKey]) ) { // Set into variables to run the comparison dataAObjectValue = deserializeJSON(dataA[currentKey]); dataBObjectValue = deserializeJSON(dataB[currentKey]); // If not equal, then end all the remaining comparisons if ( NOT dataAObjectValue.EQUALS(dataBObjectValue) ) return false; } // If equal, then remove from the structs for the final compare if ( StructKeyExists(dataA,currentKey) ) StructDelete(dataA,currentKey); if ( StructKeyExists(dataB,currentKey) ) StructDelete(dataB,currentKey); } } // Check the entire object because it is faster. //if ( dataA.EQUALS(dataB) ) // .EQUALS() fails under various CF dataType conditions // isEqual = true; // Compare the two Structs - returns true if the same isEqual = structCompare(LeftStruct=dataA,RightStruct=dataB); return isEqual; } /* Author: Ja Carter (ja@nuorbit.com) Name: $structCompare Summary: Recursive functions to compare structures and arrays. Fix by Jose Alfonso. @version 2, October 14, 2005 Based on the structCompare() by Ja Carter (ja@nuorbit.com) and Jose Alfonso Returns: Boolean Arguments: Struct - LeftStruct Struct - RightStruct Usage: structCompare(LeftStruct,RightStruct) History: 2017-01-03 - GAC - Added */ public boolean function structCompare(struct LeftStruct,struct RightStruct) { var result = true; var LeftStructKeys = ""; var RightStructKeys = ""; var key = ""; //Make sure both params are structures if (NOT (isStruct(LeftStruct) AND isStruct(RightStruct))) return false; //Make sure both structures have the same keys LeftStructKeys = ListSort(StructKeyList(LeftStruct),"TextNoCase","ASC"); RightStructKeys = ListSort(StructKeyList(RightStruct),"TextNoCase","ASC"); if(LeftStructKeys neq RightStructKeys) return false; // Loop through the keys and compare them one at a time for (key in LeftStruct) { //Key is a structure, call structCompare() if (isStruct(LeftStruct[key])){ result = structCompare(LeftStruct[key],RightStruct[key]); if (NOT result) return false; //Key is an array, call arrayCompare() } else if (isArray(LeftStruct[key])){ result = arrayCompare(LeftStruct[key],RightStruct[key]); if (NOT result) return false; // A simple type comparison here } else { if(LeftStruct[key] IS NOT RightStruct[key]) return false; } } return true; } /* Author: Ja Carter (ja@nuorbit.com) Name: $arrayCompare Summary: Recursive functions to compare arrays and nested structures. @version 1, September 23, 2004 Returns: Boolean Arguments: Array - LeftArray Array - RightArray Usage: arrayCompare(LeftArray,RightArray) History: 2017-01-03 - GAC - Added */ public boolean function arrayCompare(array LeftArray,array RightArray) { var result = true; var i = ""; //Make sure both params are arrays if (NOT (isArray(LeftArray) AND isArray(RightArray))) return false; //Make sure both arrays have the same length if (NOT arrayLen(LeftArray) EQ arrayLen(RightArray)) return false; // Loop through the elements and compare them one at a time for (i=1;i lte arrayLen(LeftArray); i = i+1) { //elements is a structure, call structCompare() if (isStruct(LeftArray[i])){ result = structCompare(LeftArray[i],RightArray[i]); if (NOT result) return false; //elements is an array, call arrayCompare() } else if (isArray(LeftArray[i])){ result = arrayCompare(LeftArray[i],RightArray[i]); if (NOT result) return false; //A simple type comparison here } else { if(LeftArray[i] IS NOT RightArray[i]) return false; } } return true; } public string function encodeAmpersands(string str="",string type="html") { var placeHolderStr = "[[-xAmp-]]"; var replaceChrs = StructNew(); var replaceStr = ""; var replaceTypeDefault = "html"; var allowedTypesList = ""; replaceChrs['html'] = "&"; replaceChrs['url'] = "%26"; replaceChrs['slash'] = "\&"; allowedTypesList= StructKeyList(replaceChrs,","); if ( !FindNoCase(arguments.type,allowedTypesList,1) ) arguments.type = replaceTypeDefault; replaceStr = replaceChrs[arguments.type]; // Preserve & that are already encoded arguments.str = replaceNoCase(arguments.str,replaceStr,placeHolderStr,"all"); // Encode the remaining '&' chars arguments.str = replace(arguments.str,"&",replaceStr,"all"); // Replace the placeHolderStr with & return replaceNoCase(arguments.str,placeHolderStr,replaceStr,"all"); } public string function decodeAmpersands(string str="",string type="html") { var replaceChrs = StructNew(); var replaceStr = ""; var replaceTypeDefault = "html"; var allowedTypesList = ""; replaceChrs['html'] = "&"; replaceChrs['url'] = "%26"; replaceChrs['slash'] = "\&"; allowedTypesList = StructKeyList(replaceChrs,","); if ( !FindNoCase(arguments.type,allowedTypesList,1) ) arguments.type = replaceTypeDefault; replaceStr = replaceChrs[arguments.type]; return replacenocase(arguments.str,replaceStr,"&","all"); } /* *************************************************************** */ /* Author: PaperThin, Inc. Name: $padNumericValue Summary: Builds a string from a number padding it with leading Zeros (0) Returns: string Arguments: Numeric - numberVal (number to be padded) Numeric - padSize (number of chars of padding) History: 2019-06-25 - GAC - Created */ public string function padNumericValue(required numeric numberVal,numeric padSize=9) { var padMaskStr = ""; // Get a safe pad size if ( IsNumeric(arguments.padSize) ) { if ( arguments.padsize GTE 2 ) arguments.padsize = arguments.padsize - 1; else arguments.padsize = 1; } // Build the string for the padding mask padMaskStr = RepeatString("0",arguments.padsize) & 9; // Pad the number Value passed in return NumberFormat( arguments.numberVal, padMaskStr ); } /* *************************************************************** */ /* Author: PaperThin, Inc. Name: $parseNumberWithUnits Summary: Returns a structure from a string parsed into 'number', 'unit' and 'orig' keys Returns: Struct number unit orig Arguments: String - str History: 2019-07-18 - GAC - Created */ struct function parseNumbersWithUnits(string str="") { var retData = {}; retData.number = 0; retData.unit = ""; retData.orig = arguments.str; if ( !isNumeric(arguments.str) ) { retData.number = VAL(arguments.str); retData.unit = REReplace(arguments.str,"(\+|\-)?[0-9]+","","all"); } else retData.number = VAL(arguments.str); return retData; } /* *************************************************************** */ /* Author: PaperThin, Inc. Name: $trimStringByWordCount Summary: Shortens a string after removing the HTML without cutting words in half and appends '...' to the end. New version enhanced to strip ALL HTML or specific HTML tags. Returns: string Arguments: String - str - The string to modify Numeric - words - The number of words to display Boolean - useEllipsis - Option to turn off the Ellipsis [...] at the end of the trimed string Boolean - stripHTML - Option to remove HTML tags before counting and trimming the string so not to leave partial html tags. String - stripTagsList - Default: [blank] - Strips all HTML Tags. Provide Comma-Delimited List of specific tags (img,p,b,strong) to strip. Leaves all others. History: 2019-12-02 - GAC - Added the stripHTML and stripTagsList parameter to allow trimming list after all HTML or specific HTML Tags have been removed. */ string function trimStringByWordCount(required string str, required numeric words, boolean useEllipsis=1, boolean stripHTML=0, string stripTagsList="") { var numWords = 0; var oldPos = 1; var i = 1; var strPos = 0; // Strip HTML if needed if ( arguments.stripHTML ) { if ( LEN(TRIM(arguments.stripTagsList)) ) { // Preserve all HTML tags, except the ones provided in the stripTagList arguments.str = tagStripper(arguments.str,'preserve',arguments.stripTagsList); } else arguments.str = tagStripper(arguments.str); } arguments.str = trim(arguments.str); arguments.str = REReplace(arguments.str,"[[:space:]]{2,}"," ","ALL"); numWords = listLen(arguments.str," "); if ( arguments.words gte numWords ) return arguments.str; for (i = 1; i lte arguments.words; i=i+1) { strPos = find(" ",arguments.str,oldPos); oldPos = strPos + 1; } if ( (len(arguments.str) lte strPos ) OR ( arguments.useEllipsis EQ false ) ) return left(arguments.str,strPos-1); else return left(arguments.str,strPos-1) & "..."; } /* tagStripper(str,action,tagList) https://cflib.org/udf/tagStripper * Function to strip HTML tags, with options to preserve certain tags. * v2 by Ray Camden, fix to closing tag * * @param str String to manipulate. (Required) * @param action Strip or preserve. Default is strip. (Optional) * @param tagList Tags to strip or perserve. (Optional) * @return Returns a string. * @author Rick Root (rick@webworksllc.com) * @version 2, July 2, 2008 */ string function tagStripper(string str) { var i = 1; var action = 'strip'; var tagList = ''; var tag = ''; if ( ArrayLen(arguments) gt 1 and lcase(arguments[2]) eq 'preserve' ) action = 'preserve'; if ( ArrayLen(arguments) gt 2 ) tagList = arguments[3]; if ( trim(lcase(action)) eq "preserve" ) { // strip only those tags in the tagList argument for (i=1;i lte listlen(tagList); i = i + 1) { tag = listGetAt(tagList,i); str = REReplaceNoCase(str,"","","ALL"); } } else { // strip all, except those in the tagList argument // if there are exclusions, mark them with NOSTRIP if ( tagList neq "" ) { for (i=1;i lte listlen(tagList); i = i + 1) { tag = listGetAt(tagList,i); str = REReplaceNoCase(str,"<(/?#tag#.*?)>","___TEMP___NOSTRIP___\1___TEMP___ENDNOSTRIP___","ALL"); } } // strip all remaining tsgs. This does NOT strip comments str = reReplaceNoCase(str,"","","ALL"); // convert unstripped back to normal str = replace(str,"___TEMP___NOSTRIP___","<","ALL"); str = replace(str,"___TEMP___ENDNOSTRIP___",">","ALL"); } return str; } /* Author: Mike Gillespie (mike@striking.com) Name: $filterInternationalChars Summary: Converts HTML entities back to their text values * Will replace chars in a string to be used to create a folder with valid equivalent replacements * @param fileName Name of file. (Required) * @return Returns a string. * @author Mike Gillespie (mike@striking.com) * @version 1, May 9, 2003 * FIXED BY 2010-01-20 - GAC Returns: String Arguments: String - str Usage: Application.ADF.data.filterInternationalChars(str) History: 2019-12-19 - GAC - Copied renamed function to data_2_0 with backwards compatibility proxy function - Updated parameter to 'str', with backwards compatibility for on paramter 'filename' - Corrected misspelled function, added a proxy function for backwards compatibility */ string function filterInternationalChars(string str=""){ // Backwards compatibility for OLD fileName parameter if ( StructKeyExists(arguments,"fileName") AND LEN(TRIM(arguments.str)) EQ 0 ) arguments.str = arguments.fileName; return super.filterInternationlChars(fileName=arguments.str); } /* filterInternationlChars - Function Name Pass-Thru for misspelled function name and bad parameter */ string function filterInternationlChars(string fileName=""){ return filterInternationalChars(str=arguments.fileName); } }