РЕДАКТИРОВАТЬ
После публикации этого ответа я уточнил его до лучшего ответа .Этот ответ оставлен, потому что он содержит большинство комментариев и служит отправной точкой для уточнения кода.
Полный код:
change.pl
:- set_prolog_flag(double_quotes, codes).
eos([], []).
dcg_change_002(Html) -->
{ Footer_start_tag = "<div class=\"footer\">" },
anything(Footer_prefix),
Footer_start_tag, !,
anything(Anchor_prefix),
anchor_2(Anchor), !,
rest_2(Rest), !,
{
string_codes(Anchor_prefix,Anchor_prefix_codes),
string_codes(Anchor,Anchor_codes),
string_codes(Rest,Rest_codes),
append(Footer_prefix,Footer_start_tag,Part_1),
append(Part_1,Anchor_prefix_codes,Part_2),
append(Part_2,Anchor_codes,Part_3),
append(Part_3,Rest_codes,Html)
}.
anything([]) --> [].
anything([C|Cs]) -->
[C],
anything(Cs).
rest_2([]) --> call(eos).
rest_2([C|Cs]) -->
\+ call(eos),
[C],
rest_2(Cs).
anchor_2("<a href=\"http://changed/something\">") --> "<a href=\"http://hardcoded/something\">".
Контрольный пример:
:- begin_tests(html_dcg).
test(002) :-
HTML_in = "\c
<body>
<a href=\"http://hardcoded/something\">This is ok</a>
<div class=\"footer\">
<a href=\"http://hardcoded/something\">Change this one</a>
</div>
</body>",
Expected_HTML_out = "\c
<body>
<a href=\"http://hardcoded/something\">This is ok</a>
<div class=\"footer\">
<a href=\"http://changed/something\">Change this one</a>
</div>
</body>",
string_codes(HTML_in,HTML_in_codes),
DCG = dcg_change_002(HTML_out_codes),
phrase(DCG,HTML_in_codes,Rest),
string_codes(HTML_out,HTML_out_codes),
format('~nHTML: ~n`~w''~n',[HTML_out]),
assertion( HTML_out == Expected_HTML_out ),
assertion( Rest == [] ).
:- end_tests(html_dcg).
Пример выполнения теста:
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.
For online help and background, visit http://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).
?- consult("C:/change.pl").
true.
?- run_tests.
% PL-Unit: html_dcg
HTML:
`<body>
<a href="http://hardcoded/something">This is ok</a>
<div class="footer">
<a href="http://changed/something">Change this one</a>
</div>
</body>'
. done
% test passed
true.
Обычно тест не выдает результат, например, format('~nHTML: ~n``~w''~n',[HTML_out])
, ноон был добавлен, чтобы вы могли видеть результат без стандартного кода в тесте.
Поскольку этот код ближе к тому, что должно быть сделано, здесь есть объяснение.
Пролог обычно пишется с использованием предикатов, они используют оператор :-
.DCG разные и используют -->
.DCG преобразуется в обычный Prolog, и DCG могут включать обычный Prolog с использованием { ... }
.
DCG-кодов символов процесса, в данном случае, поскольку это весь текст ASCII, вы можете использовать таблицу ASCII , но пытаться прочитать список символов ASCII сложно, поэтому
:- set_prolog_flag(double_quotes, codes).
сообщает компилятору, что все, что находится между " ... "
, должно быть преобразовано в список кодов символов.
тест-драйвы это так, начиная с тестового примера
:- begin_tests(html_dcg).
:- end_tests(html_dcg).
установить тестовый модуль с именем htm_dcg
и
test(002) :-
иметь тестовый предикат с именем 002
.
HTML_in = "\c
<body>
<a href=\"http://hardcoded/something\">This is ok</a>
<div class=\"footer\">
<a href=\"http://hardcoded/something\">Change this one</a>
</div>
</body>"
При этом используется = / 2 (объединение), которое не является присваиванием, для привязки текста HTML к переменной HTML_in
, но поскольку это отдельный модуль, а не код, это строкаи не конвертируется в список кодов символов.\c
- это escape-символ , который позволяет начинать <body>
со следующей строки без добавления \n
к входу.Кроме того, "
необходимо экранировать для Пролога как \"
.
Expected_HTML_out = "\c
<body>
<a href=\"http://hardcoded/something\">This is ok</a>
<div class=\"footer\">
<a href=\"http://changed/something\">Change this one</a>
</div>
</body>"
То же самое для Expected_HTML_out
.
Поскольку DCG требует коды, преобразуйте строку в коды с помощью
string_codes(HTML_in,HTML_in_codes)
На самом деле следующие две строки будут записаны как одна
phrase(dcg_change_002(HTML_out_codes),HTML_in_codes,Rest)
, но это будет немного длинно и запутанно.
фразу / 3 что является переходом от предикатов к DCG и почему я в этом примере явно записываю следующие две строки как
DCG = dcg_change_002(HTML_out_codes),
phrase(DCG,HTML_in_codes,Rest)
, чтобы вы могли видеть, что dcg_change_002/2
является DCG и возвращает HTML-результат.Он назван codes
, чтобы показать, что он возвращается как список кодов символов, а не как строка.Rest
более чем уничтожен, но используется для обнаружения некоторых редких ошибок, работающих с
assertion( Rest == [] )
Поскольку HTML возвращается в виде списка кодов символов, он преобразуется обратно в строку с
string_codes(HTML_out,HTML_out_codes)
, чтобы его можно было использовать с
format('~nHTML: ~n`~w''~n',[HTML_out])
, чтобы распечатать HTML-код для демонстрации правильной работы, и
assertion( HTML_out == Expected_HTML_out )
, чтобы показать, что код возвращает ожидаемый результат.
Что касается DCG, точка входа -
dcg_change_002(Html) -->
, и для демонстрации того, что текст можно использовать в качестве шаблона, который можно сопоставить
{ Footer_start_tag = "<div class=\"footer\">" }
Так что проблемаэто захватить весь текст до Footer_start_tag
, и это делается с помощью
anything(Footer_prefix)
, а затем совпадение с Footer_start_tag
Footer_start_tag, !,
!
- остановкаотступает и слишком продвинут для этого обсуждения, но повышает производительность, но его использование не одобряется в кругах чистоты (длинное обсуждение, не спрашивайте).
anything(Anchor_prefix)
Теперь, когда мы находимся в нижнем колонтитулевесь текст до Anchor
.
anchor_2(Anchor), !,
Здесь Anchor
находится в
anchor_2("<a href=\"http://changed/something\">") -->
"<a href=\"http://hardcoded/something\">".
, что соответствует коду, который вы хотите заменить
"<a href=\"http://hardcoded/something\">".
и вернуть код, который вы хотите изменитьэто
"<a href=\"http://changed/something\">"
Вы можете фактически составить таблицу из этих правил и изменить сразу несколько совпадений, таких как этот якорь, если вы хотите использовать одно и то же место на входе,
и, наконец,возьмите оставшуюся часть текста.
rest_2(Rest), !,
Теперь для части кода я все еще не доволен.
Так как все это в пределах { ... }
, это не DCG, а обычныйПролог встроен в DCG.
{
string_codes(Anchor_prefix,Anchor_prefix_codes),
string_codes(Anchor,Anchor_codes),
string_codes(Rest,Rest_codes),
, которые являются более преобразованиями строки в коды.
append(Footer_prefix,Footer_start_tag,Part_1),
append(Part_1,Anchor_prefix_codes,Part_2),
append(Part_2,Anchor_codes,Part_3),
append(Part_3,Rest_codes,Html)
, который складывает весь список символов обратно в один список и связывает переменную HTML
с результатом.
}.
}
просто выходит из встроенного кода.
anything([]) --> [].
anything([C|Cs]) -->
[C],
anything(Cs).
Это стандартный рекурсивный вызов, который просто захватывает отдельные символы C
и объединяет их в список, используя |
.
rest_2([]) --> call(eos).
rest_2([C|Cs]) -->
\+ call(eos),
[C],
rest_2(Cs).
Еще один стандартный рекурсивный вызов, который просто захватывает отдельные символы C
и встраивает их в список, используя |
, но этот ищет End Of Stream
, то есть eos
. \+
- это способ Пролога этого не делать.
Я знаю для многих, если не для большинства, кто читает это, это выглядит просто, но на самом деле все должно быть так просто. Причина, по которой вы не видите и не слышите, как это делают другие программисты, заключается в том, что вы изучаете программирование логики, например, Пролог сложен и даже тогда многие занятия по Прологу никогда не доходят до DCG. Мне потребовались годы, чтобы подняться на этот уровень, и даже тогда, по большинству стандартов, этот код не так хорош, он выполнит свою работу, быстр в написании и достаточно универсален.
Я надеюсь опубликовать еще более продвинутую и более простую версию, но, честно говоря, это первый раз, когда я пытался использовать DCG с HTML.