将驼峰式文本转换为标题式文本

271

在JavaScript中,如何将字符串“helloThere”或“HelloThere”转换为“Hello There”?


13
“iLiveInTheUSA”的预期输出是什么? - wim
15
我住在美国……哎呀糟糕!- 但在我的情况下,我有一组有限的字符串,并且没有这样的字符串可以破坏一个简单的转换器。不错的发现! - HyderA
同样,uSBPort 应该被翻译成“USB 端口”。 - signonsridhar
2
@wim:在正确的驼峰命名法中,iLiveInTheUSA 应该是 iLiveInTheUsa,但这会带来不同的问题。 - Konrad Höffner
1
HelloThere -> Hello There,没有句子大小写,只有标题大小写 - RichTurner
显示剩余2条评论
27个回答

361

const text = 'helloThereMister';
const result = text.replace(/([A-Z])/g, " $1");
const finalResult = result.charAt(0).toUpperCase() + result.slice(1);
console.log(finalResult);

将第一个字母大写-以此为例。请注意" $1"中的空格。
这是一个同样可以实现的TypeScript函数:
function camelCaseToWords(s: string) {
  const result = s.replace(/([A-Z])/g, ' $1');
  return result.charAt(0).toUpperCase() + result.slice(1);
}

当然,如果第一个字母已经是大写的话,你就会有多余的空格可以去掉。

1
我喜欢在text.replace中使用空格,我也会在函数调用有两个或更多参数时使用空格进行格式化以提高可读性。 - rkd
8
USBPorts翻译为U S B接口,不符合我的期望,我需要一个USB接口。 - signonsridhar
16
@signonsridhar 伙计,如果有人把小写的 usb ports 写成 uSBPorts,我会从他们的键盘上偷走 shift 键。我希望它应该是 usbPorts。在像 theUSA 这样的情况下,你可以有一个选项,比如 consecutiveCapsMode,有不同的模式:lowersplit。然后,执行 camelToSentence('theUSA', { consecutiveCapsMode: 'lower' }) 应该返回 theUsa 等。 - Nick Bull
@VladTopala 只有在第一个字母是大写的情况下才会被提及在答案中。 - ZenMaster
1
要修改此代码以处理带有数字的驼峰字符串,请改用以下模式:/([A-Z]|([0-9]+))/g - willpwa
显示剩余7条评论

195

或者使用lodash

lodash.startCase(str);

示例:

_.startCase('helloThere');
//'Hello There'

Lodash是一个很好的库,可以为许多日常js任务提供快捷方式。还有许多其他类似的字符串操作函数,例如camelCasekebabCase等。


Lodash是一个优秀的库,可以为许多日常js任务提供快捷方式。还有许多类似的字符串操作函数,如camelCasekebabCase等。

如果你尝试输出 hello world,那么输出应该是 Hello There,在这种情况下,loadash 将无法提供帮助。 - Abhishek Kumar
@AbhishekKumar,lodash的startCase函数可以将hello world转换为Hello World。具体请参考https://lodash.com/docs/4.17.15#upperFirst。 - user1696017
你说得对,兄弟。我不小心把 hello there 写成了 hello world - Abhishek Kumar
15
每次我想“肯定没有lodash也能做到这个”,它总是做得到。 - efru
1
请注意,在 v4 版本中,此函数会删除特殊字符(如ä),并将其转换为 ASCII 字符(在这种情况下是 a)。 - collerek

67

我有一个类似的问题,我是这样处理的:

stringValue.replace(/([A-Z]+)*([A-Z][a-z])/g, "$1 $2")

为了得到更健壮的解决方案:

stringValue.replace(/([A-Z]+)/g, " $1").replace(/([A-Z][a-z])/g, " $1")

http://jsfiddle.net/PeYYQ/

输入:

 helloThere 
 HelloThere 
 ILoveTheUSA
 iLoveTheUSA

输出:

 hello There 
 Hello There 
 I Love The USA
 i Love The USA

它在开头放了一个额外的空格。 - hannad rehman
10
不是OP所要求的句子格式。第一个字母应该大写。 - FirstVertex
2
此外,它在单词之间添加了额外的空格。 - Alacritas
这应该解决空间问题:stringValue.replace(/([A-Z]+)*([A-Z][a-z])/g, "$1 $2").trim() - Adam

56

没有副作用的示例。

function camel2title(camelCase) {
  // no side-effects
  return camelCase
    // inject space before the upper case letters
    .replace(/([A-Z])/g, function(match) {
       return " " + match;
    })
    // replace first char with upper case
    .replace(/^./, function(match) {
      return match.toUpperCase();
    });
}

在 ES6 中

const camel2title = (camelCase) => camelCase
  .replace(/([A-Z])/g, (match) => ` ${match}`)
  .replace(/^./, (match) => match.toUpperCase())
  .trim();

1
固态,es6代码片段+1。 - BradStell
5
这会在句子开头添加额外的空白。 - Dale Zak
@DaleZak,晚了点但还是要感谢你!我本来一直想处理这个问题,但显然社区机器人已经帮我解决了...? - renevanderark
@DaleZak 这还是一个问题吗?我在 Firefox 100x 和 Node 16x 中进行了测试,它不会添加空格。 - henroper
@DaleZak 只有在提供的字符串以大写字母开头时才会添加空格,这是无效的驼峰命名法。 - henroper

42

我发现用于测试驼峰式字符串转换为标题式字符串函数的最佳字符串是这个荒谬无意义的例子,它测试了许多边界情况。 据我所知,先前发布的所有函数都不能正确处理此字符串:

__ToGetYourGEDInTimeASongAboutThe26ABCsIsOfTheEssenceButAPersonalIDCardForUser_456InRoom26AContainingABC26TimesIsNotAsEasyAs123ForC3POOrR2D2Or2R2D

它应该转换为:

To Get Your GED In Time A Song About The 26 ABCs Is Of The Essence But A Personal ID Card For User 456 In Room 26A Containing ABC 26 Times Is Not As Easy As 123 For C3PO Or R2D2 Or 2R2D

如果您只需要一个简单的函数来处理像上面那样的情况(并且比以前的许多答案更适用于更多情况),这是我编写的函数。 这段代码并不特别优雅或快速,但它简单易懂,并且能够正常工作。

下面的片段包含一个在线可运行的示例:

var mystrings = [ "__ToGetYourGEDInTimeASongAboutThe26ABCsIsOfTheEssenceButAPersonalIDCardForUser_456InRoom26AContainingABC26TimesIsNotAsEasyAs123ForC3POOrR2D2Or2R2D", "helloThere", "HelloThere", "ILoveTheUSA", "iLoveTheUSA", "DBHostCountry", "SetSlot123ToInput456", "ILoveTheUSANetworkInTheUSA", "Limit_IOC_Duration", "_This_is_a_Test_of_Network123_in_12__days_",  "ASongAboutTheABCsIsFunToSing", "CFDs", "DBSettings", "IWouldLove1Apple", "Employee22IsCool", "SubIDIn",  "ConfigureABCsImmediately", "UseMainNameOnBehalfOfSubNameInOrders" ];

// Take a single camel case string and convert it to a string of separate words (with spaces) at the camel-case boundaries.
// 
// E.g.:
//    __ToGetYourGEDInTimeASongAboutThe26ABCsIsOfTheEssenceButAPersonalIDCardForUser_456InRoom26AContainingABC26TimesIsNotAsEasyAs123ForC3POOrR2D2Or2R2D
//                                            --> To Get Your GED In Time A Song About The 26 ABCs Is Of The Essence But A Personal ID Card For User 456 In Room 26A Containing ABC 26 Times Is Not As Easy As 123 For C3PO Or R2D2 Or 2R2D
//    helloThere                              --> Hello There
//    HelloThere                              --> Hello There 
//    ILoveTheUSA                             --> I Love The USA
//    iLoveTheUSA                             --> I Love The USA
//    DBHostCountry                           --> DB Host Country
//    SetSlot123ToInput456                    --> Set Slot 123 To Input 456
//    ILoveTheUSANetworkInTheUSA              --> I Love The USA Network In The USA
//    Limit_IOC_Duration                      --> Limit IOC Duration
//    This_is_a_Test_of_Network123_in_12_days --> This Is A Test Of Network 123 In 12 Days
//    ASongAboutTheABCsIsFunToSing            --> A Song About The ABCs Is Fun To Sing
//    CFDs                                    --> CFDs
//    DBSettings                              --> DB Settings
//    IWouldLove1Apple                        --> I Would Love 1 Apple
//    Employee22IsCool                        --> Employee 22 Is Cool
//    SubIDIn                                 --> Sub ID In
//    ConfigureCFDsImmediately                --> Configure CFDs Immediately
//    UseTakerLoginForOnBehalfOfSubIDInOrders --> Use Taker Login For On Behalf Of Sub ID In Orders
//
function camelCaseToTitleCase(in_camelCaseString) {
        var result = in_camelCaseString                         // "__ToGetYourGEDInTimeASongAboutThe26ABCsIsOfTheEssenceButAPersonalIDCardForUser_456InRoom26AContainingABC26TimesIsNotAsEasyAs123ForC3POOrR2D2Or2R2D"
            .replace(/(_)+/g, ' ')                              // " ToGetYourGEDInTimeASongAboutThe26ABCsIsOfTheEssenceButAPersonalIDCardForUser 456InRoom26AContainingABC26TimesIsNotAsEasyAs123ForC3POOrR2D2Or2R2D"
            .replace(/([a-z])([A-Z][a-z])/g, "$1 $2")           // " To Get YourGEDIn TimeASong About The26ABCs IsOf The Essence ButAPersonalIDCard For User456In Room26AContainingABC26Times IsNot AsEasy As123ForC3POOrR2D2Or2R2D"
            .replace(/([A-Z][a-z])([A-Z])/g, "$1 $2")           // " To Get YourGEDIn TimeASong About The26ABCs Is Of The Essence ButAPersonalIDCard For User456In Room26AContainingABC26Times Is Not As Easy As123ForC3POOr R2D2Or2R2D"
            .replace(/([a-z])([A-Z]+[a-z])/g, "$1 $2")          // " To Get Your GEDIn Time ASong About The26ABCs Is Of The Essence But APersonal IDCard For User456In Room26AContainingABC26Times Is Not As Easy As123ForC3POOr R2D2Or2R2D"
            .replace(/([A-Z]+)([A-Z][a-z][a-z])/g, "$1 $2")     // " To Get Your GEDIn Time A Song About The26ABCs Is Of The Essence But A Personal ID Card For User456In Room26A ContainingABC26Times Is Not As Easy As123ForC3POOr R2D2Or2R2D"
            .replace(/([a-z]+)([A-Z0-9]+)/g, "$1 $2")           // " To Get Your GEDIn Time A Song About The 26ABCs Is Of The Essence But A Personal ID Card For User 456In Room 26A Containing ABC26Times Is Not As Easy As 123For C3POOr R2D2Or 2R2D"
            
            // Note: the next regex includes a special case to exclude plurals of acronyms, e.g. "ABCs"
            .replace(/([A-Z]+)([A-Z][a-rt-z][a-z]*)/g, "$1 $2") // " To Get Your GED In Time A Song About The 26ABCs Is Of The Essence But A Personal ID Card For User 456In Room 26A Containing ABC26Times Is Not As Easy As 123For C3PO Or R2D2Or 2R2D"
            .replace(/([0-9])([A-Z][a-z]+)/g, "$1 $2")          // " To Get Your GED In Time A Song About The 26ABCs Is Of The Essence But A Personal ID Card For User 456In Room 26A Containing ABC 26Times Is Not As Easy As 123For C3PO Or R2D2Or 2R2D"  

            // Note: the next two regexes use {2,} instead of + to add space on phrases like Room26A and 26ABCs but not on phrases like R2D2 and C3PO"
            .replace(/([A-Z]{2,})([0-9]{2,})/g, "$1 $2")        // " To Get Your GED In Time A Song About The 26ABCs Is Of The Essence But A Personal ID Card For User 456 In Room 26A Containing ABC 26 Times Is Not As Easy As 123 For C3PO Or R2D2 Or 2R2D"
            .replace(/([0-9]{2,})([A-Z]{2,})/g, "$1 $2")        // " To Get Your GED In Time A Song About The 26 ABCs Is Of The Essence But A Personal ID Card For User 456 In Room 26A Containing ABC 26 Times Is Not As Easy As 123 For C3PO Or R2D2 Or 2R2D"
            .trim()                                             // "To Get Your GED In Time A Song About The 26 ABCs Is Of The Essence But A Personal ID Card For User 456 In Room 26A Containing ABC 26 Times Is Not As Easy As 123 For C3PO Or R2D2 Or 2R2D"
           ;

  // capitalize the first letter
  return result.charAt(0).toUpperCase() + result.slice(1);
}

for (var i = 0; i < mystrings.length; i++) {
  jQuery(document.body).append("<br />\"");
  jQuery(document.body).append(camelCaseToTitleCase(mystrings[i]));
  jQuery(document.body).append("\"<br>(was: \"");
  jQuery(document.body).append(mystrings[i]);
  jQuery(document.body).append("\") <br />");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.2.3/jquery.min.js"></script>


1
下划线分隔的测试用例不再起作用了,提醒一下。添加:.replace(/_/g,' ') 可以解决这个问题。此外,添加 .replace(\&\, ' & ') 支持和号拆分。 - Justin Dalrymple
1
感谢 @JustinDalrymple 指出这个问题。我们的内部代码将上面的 camelCaseToTitleCase() 函数包装在一个处理下划线的帮助程序中,所以当我发布它时没有注意到遗漏。我现在会修复上面的代码。 - Chris Kline
1
这是我迄今为止找到的最完整的解决方案。谢谢! - janhink

18

12

好的,我有点晚了几年才参与这个游戏,但我有一个类似的问题,并且我想为每个可能的输入提供一种一次替换的解决方案。我必须将大部分功劳归功于本线程中的@ZenMaster和线程中的@Benjamin Udink ten Cate。

var camelEdges = /([A-Z](?=[A-Z][a-z])|[^A-Z](?=[A-Z])|[a-zA-Z](?=[^a-zA-Z]))/g;
var textArray = ["lowercase",
                 "Class",
                 "MyClass",
                 "HTML",
                 "PDFLoader",
                 "AString",
                 "SimpleXMLParser",
                 "GL11Version",
                 "99Bottles",
                 "May5",
                 "BFG9000"];
var text;
var resultArray = [];
for (var i = 0; i < textArray.length; i++){
    text = textArray[i];
    text = text.replace(camelEdges,'$1 ');
    text = text.charAt(0).toUpperCase() + text.slice(1);
    resultArray.push(text);
}

它有三个子句,全部使用前瞻性,以防正则表达式引擎消耗过多字符:

  1. [A-Z](?=[A-Z][a-z]) 查找一个大写字母,其后跟一个大写字母和一个小写字母。这是为了结束缩写词,如USA。
  2. [^A-Z](?=[A-Z]) 查找一个非大写字母,其后跟一个大写字母。这会结束单词,如myWord和符号,如99Bottles。
  3. [a-zA-Z](?=[^a-zA-Z]) 查找一个字母,其后跟一个非字母。这将在符号之前结束单词,如BFG9000。

这个问题在我的搜索结果的顶部,所以希望我可以节省其他人的时间!


9
这是我的版本。它在每个小写英文字母后面添加一个空格,并在必要时将第一个字母大写:
例如:
thisIsCamelCase --> This Is Camel Case
this IsCamelCase --> This Is Camel Case
thisIsCamelCase123 --> This Is Camel Case123
  function camelCaseToTitleCase(camelCase){
    if (camelCase == null || camelCase == "") {
      return camelCase;
    }

    camelCase = camelCase.trim();
    var newText = "";
    for (var i = 0; i < camelCase.length; i++) {
      if (/[A-Z]/.test(camelCase[i])
          && i != 0
          && /[a-z]/.test(camelCase[i-1])) {
        newText += " ";
      }
      if (i == 0 && /[a-z]/.test(camelCase[i]))
      {
        newText += camelCase[i].toUpperCase();
      } else {
        newText += camelCase[i];
      }
    }

    return newText;
  }

8
这个实现考虑了连续的大写字母和数字。

function camelToTitleCase(str) {
  return str
    .replace(/[0-9]{2,}/g, match => ` ${match} `)
    .replace(/[^A-Z0-9][A-Z]/g, match => `${match[0]} ${match[1]}`)
    .replace(/[A-Z][A-Z][^A-Z0-9]/g, match => `${match[0]} ${match[1]}${match[2]}`)
    .replace(/[ ]{2,}/g, match => ' ')
    .replace(/\s./g, match => match.toUpperCase())
    .replace(/^./, match => match.toUpperCase())
    .trim();
}

// ----------------------------------------------------- //

var testSet = [
    'camelCase',
    'camelTOPCase',
    'aP2PConnection',
    'superSimpleExample',
    'aGoodIPAddress',
    'goodNumber90text',
    'bad132Number90text',
];

testSet.forEach(function(item) {
    console.log(item, '->', camelToTitleCase(item));
});

预期输出:
camelCase -> Camel Case
camelTOPCase -> Camel TOP Case
aP2PConnection -> A P2P Connection
superSimpleExample -> Super Simple Example
aGoodIPAddress -> A Good IP Address
goodNumber90text -> Good Number 90 Text
bad132Number90text -> Bad 132 Number 90 Text

我会使用Chris Kline的答案,它适用于类似“IP地址”这样的字符串(该函数将其转换为“I P地址”)。 - John Hamm
1
@JohnHamm,您的输入是“IP地址”,对吗?这不是驼峰命名!请在此处阅读有关驼峰命名的信息:https://en.wikipedia.org/wiki/Camel_case 不要在输入中加空格,“IPAddress”即可。此功能运行良好。 - Dipu

5

您可以使用以下类似的函数:

function fixStr(str) {
    var out = str.replace(/^\s*/, "");  // strip leading spaces
    out = out.replace(/^[a-z]|[^\s][A-Z]/g, function(str, offset) {
        if (offset == 0) {
            return(str.toUpperCase());
        } else {
            return(str.substr(0,1) + " " + str.substr(1).toUpperCase());
        }
    });
    return(out);
}

"hello World" ==> "Hello World"
"HelloWorld" ==> "Hello World"
"FunInTheSun" ==? "Fun In The Sun"

在这里使用一堆测试字符串的代码:http://jsfiddle.net/jfriend00/FWLuV/

保留前导空格的备用版本在这里:http://jsfiddle.net/jfriend00/Uy2ac/


我知道这个问题里没有要求,但是你的解决方案对于例如" helloWorld"是不起作用的。 - ZenMaster
是的,这是一个新的需求。我试图按照您最初的要求去做。无论如何,如果您不需要它们,那么快捷方式很容易去掉前导空格。如果您想让它们保持原样,也可以做到。 - jfriend00
这里有一个 jsFiddle,展示了一种使用新的 "helloWorld" require 方法并保留前导空格(如果需要)的方法:http://jsfiddle.net/jfriend00/Uy2ac/。 - jfriend00
不错。不过我在想它的性能如何。处理程序函数会在每次匹配时被调用,对吧? - ZenMaster
如果您在性能敏感的环境中需要执行大量此类操作,最好进行一些 jsperf 测试以查看哪种解决方案最快。调用回调函数并不是什么大问题。任何类型的正则表达式都很少是最快的解决方案,相对于特定目的的代码,但它们可以节省很多代码(并且通常还能避免一些错误),因此通常是首选。这取决于您的要求。 - jfriend00

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接