我有几千个.html文件,我需要搜索并将硬编码的服务器名称替换为相对路径,但仅限于页脚。
EG
<body>
<a href="http://hardcoded/something">This is ok</a>
... much more content here
<div class="footer">
<a href="http://hardcoded/something">Change this one</a>
</div>
</body>
有没有工具可以进行这种搜索和替换?
编辑
在发布这个答案后,我把它改进成了better answer。之所以留下这个答案是因为它有大部分的评论,并且是精致代码的垫脚石。
完整代码:
嫦娥.评论
:- 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])
但它被添加,所以你可以在测试中看到没有样板代码的结果。
由于这个更接近应该做的,这里是一个解释。
Prolog通常使用谓词编写,他们使用运算符:-
。 DCG是不同的,使用-->
。 DCG被翻译成常规Prolog,DCG可以通过使用{ ... }
包括常规Prolog。
DCG处理字符代码,在这种情况下因为这是所有ASCII文本,你可以使用ASCII table,但是尝试读取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 character,允许<body>
在下一行开始而不添加\n
到输入。 "
也必须作为\"
逃脱为Prolog。
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)
但这有点长而令人困惑。
phrase/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中嵌入的常规Prolog。
{
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
。 \+
是Prolog的做法。
我知道很多人,如果不是大多数读过这个,这看起来很简单,但实际上它应该那么简单。您没有看到或听到更多程序员这样做的原因是学习逻辑编程,例如Prolog,很难,甚至那时Prolog上的很多课程都没有接近DCG。我花了好几年时间才达到这个水平,即便如此,按照大多数标准来说,这个代码并不是那么好,它可以完成工作,并且编写速度快,功能多样。
我希望发布另一个更高级和更简单的版本,但实际上这是我第一次尝试使用带有HTML的DCG。
:- use_module(library(dcg/basics)).
:- set_prolog_flag(double_quotes, codes).
dcg_change_004(Html) -->
string(Footer_prefix_codes),
{ Footer_start_tag_codes = "<div class=\"footer\">" },
Footer_start_tag_codes,
string(Anchor_prefix_codes),
anchor(Anchor_codes),
remainder(Rest_codes), !,
{
flatten([Footer_prefix_codes,Footer_start_tag_codes,Anchor_prefix_codes,Anchor_codes,Rest_codes],Codes),
string_codes(Html,Codes)
}.
anchor("<a href=\"http://changed/something\">") -->
"<a href=\"http://hardcoded/something\">".
是的,代码真的那么小!
这通过将HMTL视为字符流而不是像DOM或XHTML这样的结构来实现,这极大地简化了问题。这种技术不能在所有情况下使用,但足以解决这个问题中提出的问题。
有关此技术限制的更多详细信息,请参阅this。
本version,anything//1
和rest_2//1
中使用的两个条款可以用库中的条款替换。
basics.pl -- Various general DCG utilities
该库随之添加
:- use_module(library(dcg/basics)).
代码如何工作:
阅读<div class="footer">
的所有内容
string(Footer_prefix_codes),
{ Footer_start_tag_codes = "<div class=\"footer\">" },
Footer_start_tag_codes
注意:"<div class=\"footer\">"
绑定到变量,因为它需要两次,一次用于匹配输入,一次用作输出的一部分。通过将其放在变量中,它不必键入两次。
然后阅读一切到<a href="http://hardcoded/something">
string(Anchor_prefix_codes)
并用<a href="http://changed/something">
替换它
anchor(Anchor_codes)
适用于
anchor("<a href=\"http://changed/something\">") -->
"<a href=\"http://hardcoded/something\">".
然后阅读剩下的一切。
remainder(Rest_codes)
在此过程中,DCG将字符代码收集到列表中,
Footer_prefix_codes
Footer_start_tag_codes
Anchor_prefix_codes
Anchor_codes
Rest_codes
这些被扁平化为一个列表使用
flatten([Footer_prefix_codes,Footer_start_tag_codes,Anchor_prefix_codes,Anchor_codes,Rest_codes],Codes)
并将字符代码列表Codes
转换为字符串
string_codes(Html,Codes)
以Html
为结果。
这是测试用例
:- begin_tests(html_dcg).
test(004) :-
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_004(HTML_out),
phrase(DCG,HTML_in_codes,Rest),
format('~nHTML: ~n`~w''~n',[HTML_out]),
assertion( HTML_out == Expected_HTML_out ),
assertion( Rest == [] ).
:- end_tests(html_dcg).
示例运行测试用例:
?- run_tests(html_dcg:4).
% PL-Unit: html_dcg:4
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.
DCG真的很简单。在某些方面,DCG就像BNF和正则表达式一样;在Chomsky Hierarchy中,它们比正则表达式更强大。因此,如果正则表达式让您疯狂,并且您不想使用解析器编写大量的锅炉板代码或者使用解析器解析冲突规则,请切换到DCG。
请享用。
用于在目录中搜索类型为html
的文件的Prolog代码。
test_01 :-
Directory = 'C:\\Something',
process_directory(Directory,[],Items),
print_paths(Items).
process_directory(Directory,Items0,Items) :-
directory_files(Directory,Files),
process_files(Directory,Files,Items0,Items).
process_files(Directory,[File|Files],Items0,Items) :-
process_file(Directory,File,Items0,Items1),
process_files(Directory,Files,Items1,Items), !.
process_files(_Directory,[],Items,Items).
process_file(Directory,File,Items0,Items) :-
(
File = '.',
Items = Items0
;
File = '..',
Items = Items0
;
directory_file_path(Directory, File, Path),
exists_directory(Path),
process_directory(Path,Items0,Items1),
Items = Items1
;
directory_file_path(Directory, File, Path),
exists_file(Path),
(
file_name_extension(_Name, 'html', File),
Items = [Path|Items0]
;
Items = Items0
)
;
Items = Items0
).
print_paths([Path|Paths]) :-
format('~w~n',Path),
print_paths(Paths).
print_paths([]).
这段代码由于制作测试数据的繁琐而没有检查,所以在使用之前检查一下。
如果您不确切知道自己在做什么,请在使用之前制作目录的备份副本。一个错误,它将消灭所有文件,因为它在许多目录中写入许多文件。
change_footer(Directory) :-
process_directory(Directory,[],Paths),
print_paths(Paths),
change_files(Paths).
change_files([Path|Paths]) :-
open(Path,write,Stream),
read_stream_to_codes(Stream,Codes),
DCG = dcg_change_004(HTML),
phrase(DCG,Codes),
format(Stream,HTML,[]),
close(Stream),
change_files(Paths).
change_files([]).
package custom;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
public class Main {
public static void main(String[] args) throws IOException {
String startPath = "/Users/me/someProject/public";
Files.walkFileTree(Paths.get(startPath), new SimpleFileVisitor<>(){
@Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
if (!path.toString().endsWith(".html")) {
return FileVisitResult.CONTINUE;
}
var content = new String(Files.readAllBytes(path), "UTF-8");
var fixedContent = replaceAfter(content,
"<div class=\"footer\">",
"http://hardcoded/something",
"/something");
Files.write(path, fixedContent.getBytes());
return FileVisitResult.CONTINUE;
}
});
}
/**
* Replaces the `searchTerm` with `replacement` but only after the ocurrence of `afterMark`
* If `afterMark` is not present nothing is changed
* @param source - Original string
* @param afterMark - Will perform the replacement after this mark is found
* @param searchTerm - What to replace
* @param replacement - replacement
* @return a new string with the search term replaced
*/
public static String replaceAfter(String source, String afterMark, String searchTerm, String replacement) {
var i = source.indexOf(afterMark);
if ( i < 0 ) {
return source;
}
var replaced = source.substring(i).replace(searchTerm, replacement);
return source.substring(0, i) + replaced;
}
}