Сегодня мне понадобилось перевести строку произвольного содержания в camelCase.
В интернете в основном встретились узкоспециализированные методы, которые либо переводят только имена констант (по соглашениям Java, SOME_JAVA_NAMING_CONVENTION_CONST), либо только фразы, разделенные пробелами.
Мне этого категорически не хватало, нужна была бОльшая универсальность.

Как и подобает любому уважающему себя велосипедисту, я начал писать свой алгоритм, и немного увлёкся. Очнувшись от кода, я обнаружил, что функция переводит любые мною скормленные строки в нормальный camelCase или CamelCase.
Единственное, что она не делает — не запрещает цифры в начале получившейся строки (для соглашений JNC), но мне это и не нужно было (при необходимости дописывается одной строкой кода — пополнением ко второму, вложенному, условию).

Что получилось можете увидеть под катом.


Функция принимает два аргумента — собственно строку, и флаг, указывающий писать результат с большой буквы (недо-camelCase).
Делает она всё это за один проход. Код присыпан комментариями для новичков (кому и пишется эта шпаргалка).
При жалении второй аргумент можно безболезненно откусить.
Так же практически без изменений алгоритм портируется на javascript и C#.

	/**
	 * Возвращает отформатированную в виде camelCase (или CamelCase) строку.
	 *
	 * @param string               Исходная строка
	 * @param firstWordToLowerCase Начинать ли искомую строку с маленького символа (lowercase).
	 */
	public static String toCamelCase(String string, boolean firstWordToLowerCase) {
		char currentChar, previousChar = '\u0000'; // Текущий и предыдущий символ прохода
		StringBuilder result = new StringBuilder(); // Результат функции в виде строкового билдера

		boolean firstLetterArrived = !firstWordToLowerCase; // Флаг, отвечающий за написание первого символа результата в lowercase
		boolean nextLetterInUpperCase = true; // Флаг, приказывающий следующий добавляемый символ писать в UPPERCASE

		// Проходимся по всем символам полученной строки
		for (int i = 0; i < string.length(); i++) {
			currentChar = string.charAt(i);

			/* Если текущий символ не цифробуква -
				приказываем следующий символ писать Большим (начать новое слово) и идем на следующую итерацию.
			   Если предыдущий символ это маленькая буква или цифра, а текущий это большая буква -
			    приказываем текущий символ писать Большим (начать новое слово).
			*/
			if (!Character.isLetterOrDigit(currentChar) || (
					((Character.isLetter(previousChar) && Character.isLowerCase(previousChar)) || Character.isDigit(previousChar)) &&
					Character.isLetter(currentChar) && Character.isUpperCase(currentChar))
					) {
				nextLetterInUpperCase = true;
				if (!Character.isLetterOrDigit(currentChar)) {
					previousChar = currentChar;
					continue;
				}
			}

			// Если приказано писать Большую букву, и первая буква уже написана.
			if (nextLetterInUpperCase && firstLetterArrived) {
				result.append(Character.toUpperCase(currentChar));
			}
			else {
				result.append(Character.toLowerCase(currentChar));
			}

			// Устанавливаем флаги.
			firstLetterArrived = true;
			nextLetterInUpperCase = false;
			previousChar = currentChar;
		}

		// Возвращаем полученный результат.
		return result.toString();
	}


Ну и примеры результатов функции
Source string: 'normalCamelCaseName'
Result string: 'normalCamelCaseName'
Result string: 'NormalCamelCaseName' (firstWordToLowerCase = false)
===========================
Source string: 'NotCamelCaseName'
Result string: 'notCamelCaseName'
Result string: 'NotCamelCaseName' (firstWordToLowerCase = false)
===========================
Source string: 'CONSTANT_TO_CAMEL_CASE'
Result string: 'constantToCamelCase'
Result string: 'ConstantToCamelCase' (firstWordToLowerCase = false)
===========================
Source string: 'Text To Camel Case'
Result string: 'textToCamelCase'
Result string: 'TextToCamelCase' (firstWordToLowerCase = false)
===========================
Source string: 'Text to camel case'
Result string: 'textToCamelCase'
Result string: 'TextToCamelCase' (firstWordToLowerCase = false)
===========================
Source string: 'ОтЖиМаЕмСя На ШиФфТе, ДрУзЯфФкИ!:)'
Result string: 'отЖиМаЕмСяНаШиФфТеДрУзЯфФкИ'
Result string: 'ОтЖиМаЕмСяНаШиФфТеДрУзЯфФкИ' (firstWordToLowerCase = false)
===========================
Source string: '-(*&*&%&%$^&^*()Знаков*&^%*(&$препинания… и.нечитаемых-----------знаков^ (Может*90Быть&(*?*?: СКОЛЬКО*?%?:%угодно!'
Result string: 'знаковПрепинанияИНечитаемыхЗнаковМожет90БытьСколькоУгодно'
Result string: 'ЗнаковПрепинанияИНечитаемыхЗнаковМожет90БытьСколькоУгодно' (firstWordToLowerCase = false)
===========================
Source string: 'И, напоследок, русская строка со знаками препинания (локализация!).'
Result string: 'иНапоследокРусскаяСтрокаСоЗнакамиПрепинанияЛокализация'
Result string: 'ИНапоследокРусскаяСтрокаСоЗнакамиПрепинанияЛокализация' (firstWordToLowerCase = false)

Комментарии (10)


  1. andrewnester
    08.11.2015 18:33
    +3

    ну вот честное слово, ну что здесь такого, почему Вы решили написать статью?

    можно ведь просто опубликовать на github или написать в блоге, или как вариант есть сайты со сниппетами, могли бы добавить туда


    1. vedenin1980
      08.11.2015 18:40
      +2

      Согласен, не говоря уже о том что туже функцию можно переписать раза в два короче и проще, как-то так:

          public static String toCamelCase(String string, boolean firstWordToLowerCase) {
              boolean isPrevLowerCase = false, isNextUpperCase = !firstWordToLowerCase;
              StringBuilder result = new StringBuilder();
              for (int i = 0; i < string.length(); i++) {
                  char currentChar = string.charAt(i);
                  if(!Character.isLetterOrDigit(currentChar)) {
                      isNextUpperCase = result.length() > 0 || isNextUpperCase;
                  } else {
                      result.append(
                              isNextUpperCase? Character.toUpperCase(currentChar) :
                              isPrevLowerCase ? currentChar: Character.toLowerCase(currentChar)
                      );
                      isNextUpperCase = false;
                  }
                  isPrevLowerCase = result.length() > 0 && Character.isLowerCase(currentChar);
              }
              return result.toString();
          }
      


      ИМХО, таким вещам лучше на сайтах с сниппетами, потому что если каждый будет постить на хабре каждую свою удачную на его взгляд функцию…


    1. Duster
      08.11.2015 18:48

      Поиск по гуглу выдавал говнокод, заточенный под конкретный формат исходной строки. А запросов таких достаточно много.
      Искать нечто подобное по гитхабу считаю извращением, и даже мысль не приходила искать подобное там.
      Сайты со сниппетами… Не пользовался как-то раньше, можете показать пример?
      PS: И, да, мнения разделились. Минусов ровно столько же, сколько и звезд… Значит кому-то да пригодится.


      1. stranger777
        08.11.2015 19:21
        +2

        ->тысячи их. Гуглите на английском и будет Вам счастье. Достаточно было запроса snippet sites.


      1. Borz
        09.11.2015 01:19
        +1

        А вы не думали пополнить класс org.apache.commons.lang3.StringUtils вашим методом?


  1. js605451
    08.11.2015 20:04
    +6

    Меня всегда удивляло как в одном человеке может одновременно уживаться усердие написать комментарии типа вот этих:

    // Проходимся по всем символам полученной строки

    // Устанавливаем флаги.

    // Возвращаем полученный результат.


    и потом взять и нагородить многоэтажное совершенно нечитабельное условие типа такого:
    !Character.isLetterOrDigit(currentChar) || 
    (
    	(
    		(
    			Character.isLetter(previousChar) && 
    			Character.isLowerCase(previousChar)
    		) || 
    		Character.isDigit(previousChar)
    	) &&
    	Character.isLetter(currentChar) && 
    	Character.isUpperCase(currentChar)
    )
    


    1. Duster
      08.11.2015 20:25
      -5

      Подробные комменты я пишу редко, в основном комментами отделяю смысловые «блоки» кода, как тут. Это довольно популярная практика…
      А условие вполне себе читабельное. Скобочки вынес, на строки разбил, части условия понятны и по названию.

      PS:
      1. js605451
        08.11.2015 22:04
        +4

        Вы путаете «читаемость кода» и «читаемость намерений». Код обычно читают с одной единственной целью — понять намерения автора. Форматирование и выделение блоков безусловно помогает, но это далеко не самое важное. Я легко могу прочитать условие, которое вы написали. Даже если бы вы его не отформатировали, мне ничего не стоило бы отформатировать его за вас — это тупая механическая работа. Что намного сложнее — понять ваше намерение: почему условие именно такое, что именно будет означать если условие вычисляется в true. Субъективно, хороший код как минимум даёт ответ на этот вопрос без вмешательства автора, а очень хороший — исключает возникновение такого вопроса.


  1. Borz
    09.11.2015 01:19
    +1

    как один из вариантов решения


  1. Lure_of_Chaos
    09.11.2015 01:35
    +2

    Не могу понять, зачем было писать целую статью, будто про теорему Ферма. Где здесь проблематичность, которую хабравчане могли обсуждать?