Как распарсить ячейку в excel


Часто текстовая строка может содержать несколько значений. Например, адрес компании: «г.Москва, ул.Тверская, д.13», т.е. название города, улицы и номер дома. Если необходимо определить все компании в определенном городе, то нужно «разобрать» адрес на несколько составляющих. Аналогичный подход потребуется, если необходимо разнести по столбцам Имя и фамилию, артикул товара или извлечь число или дату из текстовой строки.

Данная статья является сводной, т.е. в ней содержатся ссылки на другие статьи, в которых решены определенные задачи. Начнем с адресов.

Самый простейший случай, если адрес, состоящий из названия города, улицы и т.д., импортирован в ячейку MS EXCEL из другой информационной системы. В этом случае у адреса имеется определенная структура (если элементы адреса хранились в отдельных полях) и скорее всего нет (мало) опечаток. Разгадав структуру можно быстро разнести адрес по столбцам. Например, адрес

«г.Москва, ул.Тверская, д.13»

очевидно состоит из 3-х блоков: город, улица, дом, разделенных пробелами и запятыми. Кроме того, перед названием стоят сокращения г., ул., д. С такой задачей достаточно легко справится инструмент MS EXCEL

Текст по столбцам

. Как это сделать написано в статье

Текст-по-столбцам (мастер текстов) в MS EXCEL

.

Очевидно, что не всегда адрес имеет четкую структуру, например, могут быть пропущены пробелы (запятые все же стоят). В этом случае помогут функции, работающие с текстовыми строками. Вот эти функции:

Функция ЛЕВСИМВ() в MS EXCEL

— выводит нужное количество левых символов строки;

Функция ПРАВСИМВ() в MS EXCEL

— выводит нужное количество правых символов строки;

Функция ПСТР() в MS EXCEL

— выводит часть текста из середины строки.

Используя комбинации этих функций можно в принципе разобрать любую строку, имеющую определенную структуру. Об этом смотри статью

Разнесение в MS EXCEL текстовых строк по столбцам

.

Еще раз отмечу, что перед использованием функций необходимо понять структуру текстовой строки, которую требуется разобрать. Например, извлечем номер дома из вышеуказанного адреса. Понятно, что потребуется использовать функцию ПРАВСИМВ(), но сколько символов извлечь? Два? А если в других адресах номер дома состоит из 1 или 3 цифр? В этом случае можно попытаться найти подстроку «д.», после которой идет номер дома. Это можно сделать с помощью

функции ПОИСК()

(см. статью

Нахождение в MS EXCEL позиции n-го вхождения символа в слове

). Далее нужно вычислить количество цифр номера дома. Это сделано в файле примера , ссылка на который внизу статьи.

Усложним ситуацию. Пусть подстрока «д.» может встречаться в адресе несколько раз, например, при указании названия

деревни

используется сокращение «д.», т.е. совпадает с префиксом номера дома. В этом случае нужно определить все строки, в которых имеется название деревень (первые 2 символа, т.к. это адрес населенного пункта) и исключить их. Также можно извлечь все цифры из строки в отдельный диапазон (см. статью

Извлекаем в MS EXCEL число из конца текстовой строки

). Но, что делать, если в названии улицы есть числа? Например, «26 Бакинских комиссаров». Короче, тут начинается творчество.

Не забудьте про пробелы! Каждый пробел — это отдельный символ. Часто при печати их ставят 2 или 3 подряд, а это совсем не то же самое, что один пробел. Используйте функцию

Функция СЖПРОБЕЛЫ() в MS EXCEL

, чтобы избавиться от лишних пробелов.


Об извлечении чисел из текстовой строки

см. здесь:

Извлекаем в MS EXCEL число из начала текстовой строки

или здесь

Извлекаем в MS EXCEL число из середины текстовой строки

.


Об извлечении названия файла из полного пути

см.

Извлечение имени файла в MS EXCEL

.


Про разбор фамилии

см.

Разделяем пробелами Фамилию, Имя и Отчество

.

Часто в русских текстовых строках попадаются

английские буквы

. Их также можно обнаружить и извлечь, см.

Есть ли в слове в MS EXCEL латинские буквы, цифры, ПРОПИСНЫЕ символы

.

Все статьи сайта, связанные с преобразованием текстовых строк собраны в этом разделе:

Изменение Текстовых Строк (значений)

.

Артикул товара

Пусть имеется перечень артикулов товара: 2-3657; 3-4897; …

Как видно, артикул состоит из 2-х числовых частей, разделенных дефисом. Причем, числовые части имеют строго заданный размер: первое число состоит из 1 цифры, второе — из 4-х.

Задача состоит в том, чтобы определить артикулы, у которых левый индекс <=2 и вывести для них правый индекс.

Первая часть задачи решается формулой =—ЛЕВСИМВ(A16;1)<=2 или =ЗНАЧЕН(ЛЕВСИМВ(A16;НАЙТИ(«-«;A16;1)-1))<=2 . Вторая формула понадобится, если длина первого индекса не обязательна равна 1 (см. файл примера ).

Вторая часть задачи решается формулой =ЗНАЧЕН(ПРАВСИМВ(A16;4)) .

Зачем нам потребовалась функция ЗНАЧЕН() ? Дело в том, что текстовые функции, такие ка ПРАВСИМВ() , возвращают текст, а не число (т.е. в нашем случае число в текстовом формате). Для того, чтобы применить к таким числам в текстовом формате операцию сравнения с другим числом, т.е. <=2, потребуется сначала

преобразовать текстовый формат в числовой формат

. Самый простой для этого способ — использовать функцию ЗНАЧЕН() или попытаться применить к нему арифметическую операцию, например, двойное вычитание — или *1 или +0.

ВНИМАНИЕ!

Если у Вас есть примеры или вопросы, связанные с разбором текстовых строк — смело пишите в комментариях к этой статье или в группу

]]>
https://vk.com/excel2ru

]]> ! Я дополню эту статью самыми интересными из них.

Разбор текста регулярными выражениямиОдной из самых трудоемких и неприятных задач при работе с текстом в Excel является парсинг — разбор буквенно-цифровой «каши» на составляющие и извлечение из нее нужных нам фрагментов. Например:

  • извлечение почтового индекса из адреса (хорошо, если индекс всегда в начале, а если нет?)
  • нахождение номера и даты счета из описания платежа в банковской выписке
  • извлечение ИНН из разношерстных описаний компаний в списке контрагентов
  • поиск номера автомобиля или артикула товара в описании и т.д.

Обычно во подобных случаях, после получасового муторного ковыряния в тексте вручную, в голову начинают приходить мысли как-то автоматизировать этот процесс (особенно если данных много). Решений тут несколько и с разной степенью сложности-эффективности:

  • Использовать встроенные текстовые функции Excel для поиска-нарезки-склейки текста: ЛЕВСИМВ (LEFT), ПРАВСИМВ (RIGHT), ПСТР (MID), СЦЕПИТЬ (CONCATENATE) и ее аналоги, ОБЪЕДИНИТЬ (JOINTEXT), СОВПАД (EXACT) и т.д. Этот способ хорош, если в тексте есть четкая логика (например, индекс всегда в начале адреса). В противном случае формулы существенно усложняются и, порой, дело доходит даже до формул массива, что сильно тормозит на больших таблицах.
  • Использование оператора проверки текстового подобия Like из Visual Basic, обернутого в пользовательскую макро-функцию. Это позволяет реализовать более гибкий поиск с использованием символов подстановки (*,#,? и т.д.) К сожалению, этот инструмент не умеет извлекать нужную подстроку из текста — только проверять, содержится ли она в нем.

Кроме вышеперечисленного, есть еще один подход, очень известный в узких кругах профессиональных программистов, веб-разработчиков и прочих технарей — это регулярные выражения (Regular Expressions = RegExp = «регэкспы» = «регулярки»). Упрощенно говоря, RegExp — это язык, где с помощью специальных символов и правил производится поиск нужных подстрок в тексте, их извлечение или замена на другой текст. Регулярные выражения — это очень мощный и красивый инструмент, на порядок превосходящий по возможностям все остальные способы работы с текстом. Многие языки программирования (C#, PHP, Perl, JavaScript…) и текстовые редакторы (Word, Notepad++…) поддерживают регулярные выражения.

Microsoft Excel, к сожалению, не имеет поддержки RegExp по-умолчанию «из коробки», но это легко исправить с помощью VBA. Откройте редактор Visual Basic с вкладки Разработчик (Developer) или сочетанием клавиш Alt+F11. Затем вставьте новый модуль через меню Insert — Module и скопируйте туда текст вот такой макрофункции:

Public Function RegExpExtract(Text As String, Pattern As String, Optional Item As Integer = 1) As String
    On Error GoTo ErrHandl
    Set regex = CreateObject("VBScript.RegExp")
    regex.Pattern = Pattern
    regex.Global = True
    If regex.Test(Text) Then
        Set matches = regex.Execute(Text)
        RegExpExtract = matches.Item(Item - 1)
        Exit Function
    End If
ErrHandl:
    RegExpExtract = CVErr(xlErrValue)
End Function

Теперь можно закрыть редактор Visual Basic и, вернувшись в Excel, опробовать нашу новую функцию. Синтаксис у нее следующий:

=RegExpExtract( Txt ; Pattern ; Item )

где

  • Txt — ячейка с текстом, который мы проверяем и из которого хотим извлечь нужную нам подстроку
  • Pattern — маска (шаблон) для поиска подстроки
  • Item — порядковый номер подстроки, которую надо извлечь, если их несколько (если не указан, то выводится первое вхождение)

Самое интересное тут, конечно, это Pattern — строка-шаблон из спецсимволов «на языке» RegExp, которая и задает, что именно и где мы хотим найти. Вот самые основные из них — для начала:

 Паттерн  Описание
 . Самое простое — это точка. Она обозначает любой символ в шаблоне на указанной позиции.
 s Любой символ, выглядящий как пробел (пробел, табуляция или перенос строки).
 S Анти-вариант предыдущего шаблона, т.е. любой НЕпробельный символ.
 d Любая цифра
 D Анти-вариант предыдущего, т.е. любая НЕ цифра
 w Любой символ латиницы (A-Z), цифра или знак подчеркивания
 W Анти-вариант предыдущего, т.е. не латиница, не цифра и не подчеркивание.
[символы] В квадратных скобках можно указать один или несколько символов, разрешенных на указанной позиции в тексте. Например ст[уо]л будет соответствовать любому из слов: стол или стул.
Также можно не перечислять символы, а задать их диапазоном через дефис, т.е. вместо [ABDCDEF] написать [A-F]. или вместо [4567] ввести [4-7]. Например, для обозначения всех символов кириллицы можно использовать шаблон [а-яА-ЯёЁ].
[^символы] Если после открывающей квадратной скобки добавить символ «крышки» ^, то набор приобретет обратный смысл — на указанной позиции в тексте будут разрешены все символы, кроме перечисленных. Так, шаблон [^ЖМ]уть найдет Путь или Суть или Забудь, но не Жуть или Муть, например.
 | Логический оператор ИЛИ (OR) для проверки по любому из указанных критериев. Например чет|счёт|invoice) будет искать в тексте любое из указанных слов. Обычно набор вариантов заключается в скобки.
 ^ Начало строки
 $ Конец строки
 b Край слова

Если мы ищем определенное количество символов, например, шестизначный почтовый индекс или все трехбуквенные коды товаров, то на помощь нам приходят квантификаторы или кванторы — специальные выражения, задающие количество искомых знаков. Квантификаторы применяются к тому символу, что стоит перед ним:

  Квантор  Описание
 ? Ноль или одно вхождение. Например .? будет означать один любой символ или его отсутствие.
 + Одно или более вхождений. Например d+ означает любое количество цифр (т.е. любое число от 0 до бесконечности).
 * Ноль или более вхождений, т.е. любое количество. Так s* означает любое количество пробелов или их отсутствие.
{число} или
{число1,число2}
Если нужно задать строго определенное количество вхождений, то оно задается в фигурных скобках. Например d{6} означает строго шесть цифр, а шаблон s{2,5} — от двух до пяти пробелов

Теперь давайте перейдем к самому интересному — разбору применения созданной функции и того, что узнали о паттернах на практических примерах из жизни.

Извлекаем числа из текста

Для начала разберем простой случай — нужно извлечь из буквенно-цифровой каши первое число, например мощность источников бесперебойного питания из прайс-листа:

Извлекаем первое число из текста

Логика работы регулярного выражения тут простая: d — означает любую цифру, а квантор + говорит о том, что их количество должно быть одна или больше. Двойной минус перед функцией нужен, чтобы «на лету» преобразовать извлеченные символы в полноценное число из числа-как-текст.

Почтовый индекс

На первый взгляд, тут все просто — ищем ровно шесть цифр подряд. Используем спецсимвол d для цифры и квантор {6} для количества знаков:

Извлекаем почтовый индекс

Однако, возможна ситуация, когда левее индекса в строке стоит еще один большой набор цифр подряд (номер телефона, ИНН, банковский счет и т.д.) Тогда наша регулярка выдернет из нее первых 6 цифр, т.е. сработает некорректно:

Ошибочное извлечение индекса

Чтобы этого не происходило, необходимо добавить в наше регулярное выражение по краям модификатор b означающий конец слова. Это даст понять Excel, что нужный нам фрагмент (индекс) должен быть отдельным словом, а не частью другого фрагмента (номера телефона):

Извлекаем ровно 6-разрядное число

Телефон

Проблема с нахождением телефонного номера среди текста состоит в том, что существует очень много вариантов записи номеров — с дефисами и без, через пробелы, с кодом региона в скобках или без и т.д. Поэтому, на мой взгляд, проще сначала вычистить из исходного текста все эти символы с помощью нескольких вложенных друг в друга функций ПОДСТАВИТЬ (SUBSTITUTE), чтобы он склеился в единое целое, а потом уже примитивной регуляркой d{11} вытаскивать 11 цифр подряд:

Вытаскиваем номер телефона из текста

ИНН

Тут чуть сложнее, т.к. ИНН (в России) бывает 10-значный (у юрлиц) или 12-значный (у физлиц). Если не придираться особо, то вполне можно удовлетвориться регуляркой d{10,12}, но она, строго говоря, будет вытаскивать все числа от 10 до 12 знаков, т.е. и ошибочно введенные 11-значные. Правильнее будет использовать два шаблона, связанных логическим ИЛИ оператором | (вертикальная черта):

Извлечение ИНН из текстовой строки

Обратите внимание, что в запросе мы сначала ищем 12-разрядные, и только потом 10-разрядные числа. Если же записать нашу регулярку наоборот, то она будет вытаскивать для всех, даже длинных 12-разрядных ИНН, только первые 10 символов. То есть после срабатывания первого условия дальнейшая проверка уже не производится:

Некорректное извлечение ИНН

Это принципиальное отличие оператора | от стандартной экселевской логической функции ИЛИ (OR), где от перестановки аргументов результат не меняется.

Артикулы товаров

Во многих компаниях товарам и услугам присваиваются уникальные идентификаторы — артикулы, SAP-коды, SKU и т.д. Если в их обозначениях есть логика, то их можно легко вытаскивать из любого текста с помощью регулярных выражений. Например, если мы знаем, что наши артикулы всегда состоят из трех заглавных английских букв, дефиса и последующего трехразрядного числа, то:

Артикулы

Логика работы шаблона тут проста. [A-Z] — означает любые заглавные буквы латиницы. Следующий за ним квантор {3} говорит о том, что нам важно, чтобы таких букв было именно три. После дефиса мы ждем три цифровых разряда, поэтому добавляем на конце d{3}

Денежные суммы

Похожим на предыдущий пункт образом, можно вытаскивать и цены (стоимости, НДС…) из описания товаров. Если денежные суммы, например, указываются через дефис, то:

Извлечь стоимость (цену) из текста

Паттерн d с квантором + ищет любое число до дефиса, а d{2} будет искать копейки (два разряда) после.

Если нужно вытащить не цены, а НДС, то можно воспользоваться третьим необязательным аргументом нашей функции RegExpExtract, задающим порядковый номер извлекаемого элемента. И, само-собой, можно заменить функцией ПОДСТАВИТЬ (SUBSTITUTE) в результатах дефис на стандартный десятичный разделитель и добавить двойной минус в начале, чтобы Excel интерпретировал найденный НДС как нормальное число:

Извлекаем суммы и НДС из текста

Автомобильные номера

Если не брать спецтранспорт, прицепы и прочие мотоциклы, то стандартный российский автомобильный номер разбирается по принципу «буква — три цифры — две буквы — код региона». Причем код региона может быть 2- или 3-значным, а в качестве букв применяются только те, что похожи внешне на латиницу. Таким образом, для извлечения номеров из текста нам поможет следующая регулярка:

Извлекаем автомобильный номер из текста

Время

Для извлечения времени в формате ЧЧ:ММ подойдет такое регулярное выражение:

Извлекаем время из текста

После двоеточия фрагмент [0-5]d, как легко сообразить, задает любое число в интервале 00-59. Перед двоеточием в скобках работают два шаблона, разделенных логическим ИЛИ (вертикальной чертой):

  • [0-1]d — любое число в интервале 00-19
  • 2[0-3] — любое число в интервале 20-23

К полученному результату можно применить дополнительно еще и стандартную Excel’евскую функцию ВРЕМЯ (TIME), чтобы преобразовать его в понятный программе и пригодный для дальнейших расчетов формат времени.

Проверка пароля

Предположим, что нам надо проверить список придуманных пользователями паролей на корректность. По нашим правилам, в паролях могут быть только английские буквы (строчные или прописные) и цифры. Пробелы, подчеркивания и другие знаки препинания не допускаются.

Проверку можно организовать с помощью вот такой несложной регулярки:

Проверка пароля регулярным выражением

По сути, таким шаблоном мы требуем, чтобы между началом (^) и концом ($) в нашем тексте находились только символы из заданного в квадратных скобках набора. Если нужно проверить еще и длину пароля (например, не меньше 6 символов), то квантор + можно заменить на интервал «шесть и более» в виде {6,}:

Проверка длины пароля

Город из адреса

Допустим, нам нужно вытащить город из строки адреса. Поможет регулярка, извлекающая текст от «г.» до следующей запятой:

Извлечь город из адреса

Давайте разберем этот шаблон поподробнее.

Если вы прочитали текст выше, то уже поняли, что некоторые символы в регулярных выражениях (точки, звездочки, знаки доллара и т.д.) несут особый смысл. Если же нужно искать сами эти символы, то перед ними ставится обратная косая черта (иногда это называют экранированием). Поэтому при поиске фрагмента «г.» мы должны написать в регулярке г. если ищем плюсик, то + и т.д.

Следующих два символа в нашем шаблоне — точка и звездочка-квантор — обозначают любое количество любых символов, т.е. любое название города.

На конце шаблона стоит запятая, т.к. мы ищем текст от «г.» до запятой. Но ведь в тексте может быть несколько запятых, правда? Не только после города, но и после улицы, дома и т.д. На какой из них будет останавливаться наш запрос? Вот за это отвечает вопросительный знак. Без него наша регулярка вытаскивала бы максимально длинную строку из всех возможных:

Извлекаем город из адреса - жадный квантор

В терминах регулярных выражений, такой шаблон является «жадным». Чтобы исправить ситуацию и нужен вопросительный знак — он делает квантор, после которого стоит, «скупым» — и наш запрос берет текст только до первой встречной запятой после «г.»:

Скупой квантор

Имя файла из полного пути

Еще одна весьма распространенная ситуация — вытащить имя файла из полного пути. Тут поможет простая регулярка вида:

Извлечь имя файла из полного пути

Тут фишка в том, что поиск, по сути, происходит в обратном направлении — от конца к началу, т.к. в конце нашего шаблона стоит $, и мы ищем все, что перед ним до первого справа обратного слэша. Бэкслэш заэкранирован, как и точка в предыдущем примере.

P.S.

«Под занавес» хочу уточнить, что все вышеописанное — это малая часть из всех возможностей, которые предоставляют регулярные выражения. Спецсимволов и правил их использования очень много и на эту тему написаны целые книги (рекомендую для начала хотя бы эту). В некотором смысле, написание регулярных выражений — это почти искусство. Почти всегда придуманную регулярку можно улучшить или дополнить, сделав ее более изящной или способным работать с более широким диапазоном вариантов входных данных.

Для анализа и разбора чужих регулярок или отладки своих собственных есть несколько удобных онлайн-сервисов: RegEx101, RegExr и др.

К сожалению, не все возможности классических регулярных выражений поддерживаются в VBA (например, обратный поиск или POSIX-классы) и умеют работать с кириллицей, но и того, что есть, думаю, хватит на первое время, чтобы вас порадовать.

Если же вы не новичок в теме, и вам есть чем поделиться — оставляйте полезные при работе в Excel регулярки в комментариях ниже. Один ум хорошо, а два сапога — пара!

Ссылки по теме

  • Замена и зачистка текста функцией ПОДСТАВИТЬ (SUBSTITUTE)
  • Поиск и подсветка символов латиницы в русском тексте
  • Поиск ближайшего похожего текста (Иванов = Ивонов = Иваноф и т.д.)

General

1 — The first line in UserSelectedRange is setting the return value to its default. At this point in the function it is already Nothing:

Private Function UserSelectRangeO(ByRef lastRow As Long) As Range
    Set UserSelectRange = Nothing    '<- Does nothing

Similarly, in GetUserInputRange() you do this if Application.InputBox throws:

    '...
    Exit Function
InputError:
    Set GetUserInputRange = Nothing
End Function

But if it throws, GetUserInputRange is never set. This function can be simplified to…

Private Function GetUserInputRange() As Range
    'This is segregated because of how excel handles cancelling a range input
    On Error Resume Next
    Set GetUserInputRange = Application.InputBox("Please click a cell in the column to parse", _
                                                 "Column Parser", Type:=8)
End Function

…and at that point I’m not sure I see why you wouldn’t just inline it because you are using the return value of Nothing to throw a different error anyway:

If columnToParse Is Nothing Then Err.Raise ParseError.InputRangeIsNothing

2 — There isn’t any need for UserSelectedRange to return lastRow by reference. You can simply get the last row from the selected Range itself. Since you aren’t even using lastRow in ParseIntoColumns, it allows you to get rid of this dead code in that procedure:

Dim lastRow As Long
lastRow = 1

3 — MsgBox returns a VbMsgBoxResult, which is an Integer. When you make tests of the return value, you are implicitly cast it to a String, then comparing it to an Integer (vbCancel), which implicitly casts it back to an Integer:

Dim result As String
result = MsgBox("The column you've selected to parse is column " & columnLetter, vbOKCancel)
If result = vbCancel Then Err.Raise ParseError.ProcessCancelled

If you need to store the return value, declare it as the appropriate type:

Dim result As VbMsgBoxResult

If you don’t (for example if you’re only testing it once), you can simply omit the variable declaration and test the return value directly:

If MsgBox("The column you've selected to parse is column " & columnLetter, _
          vbOKCancel) = vbCancel Then
    Err.Raise ParseError.ProcessCancelled
End If

4 — I’d put your ParseError enumeration in its own module and make it public instead of private. That way if you have other procedures that use custom error numbers you both easily can reuse them and avoid the possibility of collisions in error numbers.

5 — Named parameters after line continuations should be indented consistently. This is incredibly difficult to read:

workingRange.TextToColumns _
Destination:=workingRange, _
DataType:=xlDelimited, _
    TextQualifier:=xlTextQualifierNone, _
    ConsecutiveDelimiter:=True, _
    Tab:=False, _
    Semicolon:=False, _
    Comma:=False, _
    Space:=False, _
    Other:=True, OtherChar:=vbLf

6 — Consider using a regular expression to remove duplicate line feeds in ParseIntoRows. This can also avoid the possible bug if the data contains a vbCr. Since you immediately split the result, I’d use a function like this…

'Needs a reference to Microsoft VBScript Regular Expressions x.x
Private Function SplitLinesNoEmpties(target As String) As String()
    With New RegExp
        .Pattern = "[n]+"
        .MultiLine = True
        .Global = True
        SplitLinesNoEmpties = Split(.Replace(Replace$(target, vbCr, vbLf), vbLf), vbLf)
    End With
End Function

…instead of: Do Until cellContent = replacementCellContent

Then you can simply use stringParts = SplitLinesNoEmpties(cellContent) to get your array.

7 — Guard clauses should be in the procedure that they guard — not in the calling procedure. I’d move this code…

stringParts = Split(cellContent, vbLf)
numberOfParts = UBound(stringParts) - LBound(stringParts) + 1
If numberOfParts > 1 Then CreateNewRows stringParts(), numberOfParts, cellToParse

…to Sub CreateNewRows:

Private Sub CreateNewRows(ByRef partsOfString() As String, ByVal cellToParse As Range)
    Dim bottom As Long
    Dim top As Long
    bottom = LBound(partsOfString)
    top = UBound(partsOfString)
    If top <= bottom Then Exit Sub

    With cellToParse
        .EntireRow.Copy
        .Offset(1, 0).Resize(top - bottom, 1).EntireRow.Insert
        .Resize(numberOfParts, 1).Value = Application.WorksheetFunction.Transpose(partsOfString)
    End With
End Sub

Note that this does a couple things — it avoids the need to add one to the UBoundLBound calculation and then just subtract it again. If you’re testing to see if an array has at least 2 elements, UBound > LBound is sufficient (and protects from cases where LBound andor UBound is negative). It also explicitly protects against the case of UBound(Split(vbNullString)), which returns -1. This leads me to…

8 — Your guard clauses have a very subtle bug. Before you process the cell, you use this test:

Set cellToParse = Cells(currentRow, workingColumn)
If Not IsEmpty(cellToParse) Then
    cellContent = cellToParse.Value
    '...

IsEmpty isn’t doing what you think it is here. It doesn’t test whether a cell is empty — it tests whether the Variant passed to it is equal to vbEmpty.

Private Sub TleBug()
    Cells(1, 1).Formula = "=" & Chr$(34) & Chr$(34)  ' =""
    Debug.Print IsEmpty(Cells(1, 1))                 'False
    Debug.Print Cells(1, 1).Value = vbNullString     'True
End Sub

If you need to test whether a cell evaluates to vbNullString, do it explicitly:

Set cellToParse = Cells(currentRow, workingColumn)
cellContent = cellToParse.Value
If cellToParse <> vbNullString Then
    '...

9 — You have another (less) subtle bug. If you use Application.InputBox to have the user select the range to work with, you can’t use the global Range or Cells collections — they have to be qualified. The reason is that you yield control to the user, who is free to select a cell in a different Workbook than the one that was active when the macro started.

Private Sub TleBugTwo()
    Dim target As Range
    'User selects a cell in a different Workbook
    Set target = Application.InputBox("Select cell", "Input", Type:=8)
    Dim globalRange As Range
    Set globalRange = Range("A1")
    Debug.Print globalRange.Worksheet Is ActiveSheet      'True
    Debug.Print target.Worksheet Is globalRange.Worksheet 'False
End Sub

User Interface

1. GetUserInputRange() doesn’t display appropriate errors

If the user simply hits «OK» when the Application.InputBox is displayed, Excel shows this error dialog:

Error if empty

2. The range selection interface duplicates Excel functionality

Note that this is more a matter of personal preference than anything, but if Excel already provides an interface to select a cell or range of cells, why duplicate that? I’d simply use the existing Selection object when the macro starts. You’re already prompting the user to confirm that the Range that they selected when prompted is the one they want to work on, so why not just skip that entire process and use the Selection object instead?

Errors

@Zak already addressed the big issue with the error handling, so I’ll nitpick a little instead.

1. Duplicated code

Your error handlers in ParseIntoColumns and ParseIntoRows are identical, and only display the error condition to the user. I’d recommend extracting that section to it’s own Sub:

Private Sub DisplayErrorMessage(Err As Object)
    Select Case Err.Number
        Case ParseError.InputRangeIsNothing
            MsgBox "Process cancelled: You have not selected a range.", vbExclamation
        Case ParseError.MultipleColumnsSelected
            MsgBox "Process cancelled: You may not select more than 1 column at a time", vbExclamation
        Case ParseError.ProcessCancelled
            MsgBox "Process cancelled", vbExclamation
        Case ParseError.NoOverwrite
            MsgBox "Process cancelled: Please alter your data structure to allow overwriting cells to the right of your selection.", vbExclamation
        Case ParseError.NoData
            MsgBox "Process cancelled: your selection does not have data to parse", vbExclamation
        Case Else
            MsgBox "An error has occured: " & Err.Number & "- " & Err.Description, vbCritical
    End Select
End Sub

Then you can simply do this for your error handlers:

CleanUp:
    'Do stuff
    Exit Sub
ErrHandler:
    DisplayErrorMessage Err
    Resume CleanUp

2. User cancellation is not an error condition

I’d consider this section to be an abuse of the error handler:

Dim confirmOverwrite As String
confirmOverwrite = MsgBox("Do you want to overwrite all data to the right of your selection?", vbYesNo)
If confirmOverwrite = vbNo Then Err.Raise ParseError.NoOverwrite

I’m not even sure that you need to display any sort of confirmation that the process has been cancelled. My personal expectation would be that it would simply exit after I told it not to continue:

If confirmOverwrite = vbNo Then Exit Sub

Здравствуйте!
Заранее извиняюсь за не совсем ясное, как по мне, объяснение своей проблемы.
В вузе работаю над проектом, суть которого заключается в парсинге данных из Excel с последующем сохранением в PostgreSQL.
Мне дали таблицу, попросили написать для неё программу. Я написал, данные успешно парсились. Но затем мне скинули ещё с десяток таблиц, и вот тут начались проблемы. Таблицы несколько отличаются в том плане, что данные, которые в первой таблице находятся на n-ой строчке и j-ом столбце (1 скриншот), в других таблицах могут находиться в иных местах (2 скриншот).

Скриншоты

5ffe96f2ce1e8098016565.png
5ffe970b1b362024088238.png

И таких не совпадающих по координатам ячеек в таблицах достаточно много.
А я написал программу, которая начинает парсить данные с конкретного столбца и конкретной строчки конкретной таблицы, ибо предполагал, что таблицы по структуре будут одинаковы.

Вопрос: как грамотно написать парсер таким образом, чтобы он не был привязан к определенным строчкам и столбцам при поиске конкретных данных и, соответственно, не ломался, если нужные данные в таблице находятся, условно говоря, в ячейке C16, а не B16, как предполагалось. Как можно учесть все эти несоответствия?
Спрашиваю не потому, что сам не хочу напрягаться, а потому, что меня самого интересует, как можно написать такую «адаптивную» программу без костылей с кучей if-else и циклов for, и возможно ли такое в принципе.

Не знаю, нужна эта информация или нет, но:
1) Использую язык Java и библиотеку apache.poi.
2) Сам проект на GitHub

В данном режиме парсер берет все непустые значения из указанного столбца (по умолчанию из столбца 1 «А»), начиная с указанной строки (по умолчанию — со второй строки).

Доступные опции:

Обрабатывать только выделенные строки

Если данная опция включена, то парсер будет обрабатывать только выделенные строки или строки, которые содержат выделенные ячейки.

Другими словами необязательно выделять строку целиком, достаточно выделить ячейки в одном столбце, причем необязательно в том, где находятся исходные данные.

Например на скриншоте мы выделили диапазон ячеек B5:B8. При включенном режиме «Обрабатывать только выделенные строки» и указанном столбце 1 «А», в качестве исходных данных у нас будет 4 ссылки из первого столбца.

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

Брать отображаемый текст ячеек (а не значения)

Данная опция по умолчанию выключена, и парсер берет значение ячейки (а не отображаемый текст). Опция может понадобиться при использовании в ячейках нестандартных форматов отображения данных.

Например здесь в ячейке А1 отображается 000100, а значение ячейки равно 100 (что видно в строке формул выше). Это может быть почтовый индекс или артикул автомобильной запчасти. Если галка опции Брать отображаемый текст ячеек (а не значения) установлена, то из данной ячейки будет взята строка 000100, а если галка опции отключена (по умолчанию), то будет взято число 100.

В случае, если ячейка содержит гиперссылку, то значением для парсера считается именно гиперссылка, а не то, что отображается в ней. Смотрим пример.

Предположим, заданный столбец для парсера 2 «В». Если галка опции Брать отображаемый текст ячеек (а не значения) установлена, то из ячейки В2 будет взято число 2500. Если галка снята (значение по умолчанию), то будет взята гиперссылка. 

Обрабатывать только строки, где значение столбца … равно …

При включении данной опции парсер берет в исходные значения только те строки, которые соответствуют заданному условию. Разберем на примере, как можно использовать данную опцию.

Для всех вариантов Исходные данные находятся в столбце 1 «А».

Вариант №1. Обрабатывать только строки, где значение столбца 1 «А» равно http*

Здесь мы выбрали для проверки тот же столбец, что является у нас исходным, и указали парсеру брать все строки, где значение начинается с http. Звездочка в данном случае обозначает любой символ. В качестве исходных данных будут взяты 9 ячеек со ссылками.

Вариант №2. Обрабатывать только строки, где значение столбца 2 «В» равно 

Здесь мы будем сверяться по соседнему столбцу 2 «В». Поле правее слова «равно» мы оставили пустым. Т.е. парсер обрабатывает только те строки, где пустой столбец 2 «В». В качестве исходных данных будут взяты последние 6 строк, у которых пустой соседний столбец.

Данный вариант удобно использовать, когда надо обработать большой список значений в несколько заходов. Парсер после обработки ссылки может ставить в соседний столбец какую-нибудь метку. При повторном запуске мы прописываем условие как в данном варианте №2, и парсер будет обрабатывать только те строки, где метки не стоит.

Вариант №3. Обрабатывать только строки, где значение столбца 2 «В» равно ?*

В данном случае мы используем подстановочные символы. Звездочка, как мы знаем обозначает любой символ или отсутствие символов. Знак вопроса означает любой символ. Таким образом мы говорим парсеру обрабатывать строки, где в соседнем столбце 2 «В» есть хотя бы один символ (или больше). Будут обработаны только первые 5 строк (если мы начинаем со второй строки).

Еще один подстановочный символ, который можно использовать в поле правее слова «равно» — # — обозначает любую цифру.

Вариант №4. Обрабатывать только строки, где значение столбца 3 «С» равно шт

Для данного варианта свой пример таблицы (см. скриншот ниже). В поле правее слова «равно» можно вписывать любые значения с использованием подстановочных символов * ? # или без них. Указанный вариант №4 может быть использован, например, в прайсе, где вперемешку идут названия категорий и сами товары.

В данном примере будут обработаны только 10 строк, соответствующие условию.

Понравилась статья? Поделить с друзьями:

А вот еще интересные статьи:

  • Как распечатать word не с первой страницы
  • Как распарсить текст в excel
  • Как распечатать word в изображение
  • Как распарсить таблицу excel
  • Как распечатать word 2003

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии