Найкращий спосіб розбору файлу


9

Я намагаюся знайти краще рішення для розбору парних знаменитих форматів файлів, таких як: EDIFACT та TRADACOMS .

Якщо ви не знайомі з цими стандартами, перегляньте цей приклад з Вікіпедії:

Нижче див. Приклад повідомлення EDIFACT, яке використовується для відповіді на запит про доступність продукту: -

UNA:+.? '
UNB+IATB:1+6XPPC+LHPPC+940101:0950+1'
UNH+1+PAORES:93:1:IA'
MSG+1:45'
IFT+3+XYZCOMPANY AVAILABILITY'
ERC+A7V:1:AMD'
IFT+3+NO MORE FLIGHTS'
ODI'
TVL+240493:1000::1220+FRA+JFK+DL+400+C'
PDI++C:3+Y::3+F::1'
APD+714C:0:::6++++++6X'
TVL+240493:1740::2030+JFK+MIA+DL+081+C'
PDI++C:4'
APD+EM2:0:130::6+++++++DA'
UNT+13+1'
UNZ+1+1'

Сегмент UNA необов’язковий. Якщо він присутній, він вказує спеціальні символи, які слід використовувати для інтерпретації залишків повідомлення. У цьому порядку підписано шість символів:

  • роздільник компонентів даних (у цьому прикладі)
  • роздільник елементів даних (+ у цьому зразку)
  • десяткове повідомлення (у цьому зразку)
  • символ випуску (? у цьому зразку)
  • зарезервовано, повинно бути пробіл
  • термінатор сегмента ('у цьому прикладі)

Як ви бачите, це лише деякі дані, відформатовані особливим чином, очікуючи їх розбору (подібно до файлів XML ).

Тепер моя система побудована на PHP, і мені вдалося створити аналізатор, використовуючи регулярні вирази для кожного сегменту, але проблема полягає не в тому, що всі реалізують стандарт ідеально.

Деякі постачальники схильні повністю ігнорувати необов'язкові сегменти та поля. Інші можуть вирішити надіслати більше даних, ніж інші. Ось чому я був змушений створити валідатори для сегментів та полів, щоб перевірити, чи файл був правильним чи ні.

Ви можете уявити собі кошмар регулярних виразів, які я зараз маю. Крім того, кожен постачальник потребує багатьох модифікацій регулярних виразів, які я схильний створювати аналізатор для кожного постачальника.


Запитання:

1- Це найкраща практика для розбору файлів (з використанням регулярних виразів)?

2- Чи є краще рішення для розбору файлів (можливо, є готові рішення там)? Чи вдасться показати, який сегмент відсутній або файл пошкоджений?

3- Якщо мені доведеться все-таки створити свій аналізатор, яку модель дизайну чи методологію я повинен використовувати?

Примітки:

Я десь читав про yacc та ANTLR, але не знаю, відповідають вони моїм потребам чи ні!


Побачивши цю граматику EDIFACT, парсери та бібліотеки (Java), мені цікаво, чи вдасться використовувати лексери / аналізатори. Якби це я, я спробував би спочатку комбінатор парсера. :)
Гай Кодер

Відповіді:


18

Що вам потрібно - це справжній парсер. Регулярні вирази обробляють лексінг, а не розбір. Тобто вони ідентифікують маркери у вашому потоці введення. Парсинг - це контекст жетонів, тобто, хто йде куди і в якому порядку.

Класичним інструментом розбору є yacc / bison . Класичним лексером є lex / flex . Оскільки php дозволяє інтегрувати код C , ви можете використовувати flex та bison для складання свого парсера, запросити php викликати його на вхідному файлі / потоці, а потім отримати результати.

Це буде палаючий швидко , і працювати набагато простіше, коли ви зрозумієте інструменти . Пропоную прочитати Lex та Yacc 2nd Ed. від О'Рейлі. Для прикладу, я створив флекс-бізон-проект на github , з makefile. Він необхідний для хрестовини, якщо це необхідно.

Це є складним, але , як ви дізналися, що вам потрібно зробити , це комплекс. Існує велика кількість «штучок», які необхідно зробити для належно працюючого аналізатора, а флекс та зубр мають справу з механічними бітами. В іншому випадку ви опинитесь у незавидному положенні написання коду на тому ж шарі абстракції, що і збірка.


1
+1 Відмінна відповідь, особливо зважаючи на те, що йдеться з аналізатором зразків.
Калеб

@caleb дякую, я багато працюю з flex / bison, але пристойних (читайте: складних) прикладів дуже мало. Це не найкращий синтаксичний аналіз, оскільки коментарів не так багато, тож сміливо надсилайте оновлення.
Спенсер Ратбун

@SpencerRathbun дуже дякує за вашу детальну відповідь та приклад. Я не маю жодних знань, що стосується будь-якої з згаданих вами термінологій (yacc / bison, lex / flex, ... і т. Д.), Оскільки я мій досвід, головним чином, стосується веб-розробки. Чи достатньо "Lex and Yacc 2nd Ed", щоб я все зрозумів і створив хороший аналізатор? чи є інші теми та матеріали, які я повинен висвітлити спочатку?
Сонго

@songo Книга охоплює всі релевантні деталі і є досить короткою, розміщуючи приблизно на 300 сторінках середнього розміру. Він не охоплює використання дизайну c або мови . На щастя, доступно багато посилань на c, наприклад K&R Мова програмування на C, і вам не потрібно розробляти мову, просто дотримуйтесь стандартів, на які ви посилаєтесь. Зауважте, що читання обкладинки для обкладинки рекомендується, оскільки автори щось згадають один раз, і припустимо, якщо вам це потрібно, ви повернетесь і перечитайте. Таким чином ви нічого не пропустите.
Спенсер Ратбун

Я не думаю, що стандартний лексер може обробляти динамічні роздільники, які може вказати лінія UNA. Тож принаймні вам знадобиться лексер із символами, що настроюються під час виконання, для 5-ти роздільників.
Кевін

3

ой .. "справжній" аналізатор? державні машини ??

вибачте, але я перетворився з академічного на хакерського з моменту початку своєї роботи .. тому я б сказав, що існують більш прості шляхи .. хоча, можливо, не так, як вдосконалено академічно :)

Я спробую запропонувати альтернативний підхід, з яким дехто може погодитись, а може і не погодитися, але це МОЖЕ бути дуже практичним у робочих умовах.

Я б;

loop every line
   X = pop the first 3 letters of line
   Y = rest of line
   case X = 'UNA':
       class init (Y)

звідти я би використовував класи для типів даних. розділення розділювачів компонентів та елементів та повторення над поверненими масивами.

Для мене це повторне використання коду, OO, низька згуртованість і дуже модульна .. та легко налагоджувати та програмувати. простіше, тим краще.

щоб проаналізувати файл, який вам не потрібен державних машин або щось зовсім складне .. Державні машини добре підходять для розбору коду, ви здивуєтеся тим, наскільки потужним може бути вищевказаний код пседуо при використанні в контексті ОО.

пс. я раніше працював з дуже схожими файлами :)


Більше псевдокоду розміщено тут:

клас

UNA:

init(Y):
 remove ' from end
 components = Y.split(':') 
 for c in components
     .. etc..

 getComponents():
   logic..
   return

 getSomethingElse():
   logic..
   return

class UNZ:
   ...

Parser(lines):

Msg = new obj;

for line in lines
   X = pop the first 3 letters of line
   Y = rest of line
   case X = 'UNA':
      Msg.add(UNA(Y))

msg.isOK = true
return Msg

Ви можете потім використовувати його так.

msg = Main(File.getLines());
// could put in error checking
// if msg.isOK:
msg.UNA.getSomethingElse();

і скажіть, що у вас є більше одного сегмента .. використовуйте чергу, щоб додати їх і отримати перший, другий тощо. Ви дійсно просто представляєте msg в obj і надаєте об'єктним методам виклику даних. Ви можете скористатися цим, створивши також власні методи .. для успадкування .. ну це вже інше питання, і я думаю, ви можете легко застосувати його, якщо ви це розумієте


3
Я робив це раніше, і виявив, що його недостатньо для будь-якого, що перевищує один або два випадки recognize X token and do Y. Немає контексту, у вас не може бути декілька станів, проходження повз тривіальної кількості випадків роздуває код, а обробка помилок складна. Я вважаю, що мені потрібні ці особливості в реальному світі майже у всіх випадках. Це залишає в стороні помилки в цьому в міру зростання складності. Найважче - це встановити каркас та дізнатися, як працює інструмент. Поминьте це, і так само швидко щось збити.
Спенсер Ратбун

це повідомлення, які штати вам потрібні? Здавалося б, таке повідомлення, яке організоване в структурі композитів та сегментів, цілком би відповідало цьому підходу ОО. поводження з помилками проводиться для кожного класу і правильно виконано ви можете побудувати аналізатор, який є дуже ефективним і розширюваним. такі повідомлення, як цей, піддаються класам і функціям, особливо коли кілька постачальників надсилають різні смаки одного формату. Прикладом може бути функція класу UNA, яка повернула певне значення для конкретного постачальника.
Росс

@Ross тому в основному ви будете мати «UNA клас» для сегмента «УНА» та всередині нього буде синтаксичного аналізу метод для кожного постачальника ( parseUNAsegemntForVendor1(), parseUNAsegemntForVendor2(), parseUNAsegemntForVendor3(), ... і т.д.), НЕ так?
Сонго

2
@Ross У розділі повідомлення є розділи, дійсні в різних точках під час розбору. Це держави, про які я говорив. Дизайн OO розумний, і я не кажу, що він не буде працювати . Я наполягаю на флексі та зубрах, тому що, подібно до концепцій функціонального програмування, вони краще підходять, ніж інші інструменти, але більшість людей вважають, що вони занадто складні, щоб не турбувати навчання.
Спенсер Ратбун

@Songo .. ні, ви б розбиралися незалежно від постачальника (якщо ви не новий, хто). синтаксичний розбір буде в класі INIT. Ви перетворюєте своє повідомлення в об’єкт даних на основі тих же правил, що використовуються для побудови повідомлення. Якщо вам потрібно було взяти щось із повідомлення, однак .. і воно представлене по-різному у ваших постачальників, тоді у вас були б різні функції так .. Але навіщо це так? використовувати базовий клас і мати окремий клас для кожного постачальника, переосмислюючи його лише за потреби, набагато простіше. скористатися спадщиною.
Росс

1

Ви пробували гуглінг для "PHP EDIFACT"? Це один із перших результатів, який з’явився: http://code.google.com/p/edieasy/

Хоча це може бути недостатньо для вашого випадку використання, ви, можливо, зможете отримати деякі ідеї з нього. Мені не подобається код з його безліччю вкладених циклів і умов, але це може бути початком.


1
Я перевірив багато проектів там, але проблема полягала в основному в різних реалізаціях постачальників за допомогою стандарту. Я можу змусити одного постачальника надіслати мені певний сегмент, але я можу вважати його необов'язковим для іншого постачальника. Ось чому я, мабуть, мені знадобиться створити власний індивідуальний аналізатор.
Сонго

1

Оскільки, згадавши про Yacc / Bison + Flex / Lex, я можу також закинути одну з інших основних альтернатив: парсерні комбінатори. Вони популярні у функціональному програмуванні, як у Haskell, але якщо ви можете інтерфейсувати код C, ви можете їх використовувати, і що ви знаєте, хтось теж написав його для PHP. (Я не маю досвіду роботи з цією конкретною реалізацією, але якщо вона працює як більшість з них, це має бути досить приємно.)

Загальна концепція полягає в тому, що ви починаєте з набору невеликих, легких для визначення парсерів, зазвичай токенізаторів. Начебто ви мали б одну функцію аналізатора для кожного з 6 згаданих вами елементів даних. Потім ви використовуєте комбінатори (функції, що поєднують функції), щоб зробити більші парсери, які захоплюють більші елементи. Як необов'язковий сегмент був би optionalкомбінатор, що працює на аналізаторі сегментів.

Не впевнений, наскільки добре це працює в PHP, але це цікавий спосіб написати парсер, і мені дуже подобається використовувати їх іншими мовами.


0

замість того, щоб сповзати з реджексами, зробіть власну державну машину

це стане читабельнішим (і матимете змогу мати кращі коментарі) у нетривіальних ситуаціях, і буде простіше налагодити цю чорну скриньку, що перераховується


5
Швидка примітка, це те, що флекс та зубр роблять під кришкою. Тільки вони роблять це правильно .
Спенсер Ратбун

0

Я не знаю, що ви хочете точно робити з цими даними згодом, і якщо це не кувалда для горіха, але я мав хороший досвід роботи з Елі . Ви описуєте лексичні фрази, а потім конкретний / абстрактний синтаксис і генеруєте те, що хочете генерувати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.