34
35
36:- module(html_text,
37 [ html_text/1, 38 html_text/2 39 ]). 40:- use_module(library(debug),[debug/3]). 41:- autoload(library(ansi_term),[ansi_format/3]). 42:- autoload(library(apply),[foldl/4,maplist/3,maplist/2]). 43:- autoload(library(error),[must_be/2]). 44:- autoload(library(lists),
45 [ append/3, list_to_set/2, reverse/2, delete/3, sum_list/2,
46 nth1/3, max_list/2
47 ]). 48:- autoload(library(option),[select_option/4,merge_options/3,option/3]). 49:- autoload(library(sgml),[xml_is_dom/1,load_html/3]). 50:- autoload(library(lynx/format),[format_paragraph/2,trim_line/2]). 51:- autoload(library(lynx/html_style),
52 [ element_css/3, css_block_options/5, css_inline_options/3,
53 attrs_classes/2, style_css_attrs/2
54 ]). 55
56:- predicate_options(html_text/2, 2,
57 [ margin_left(integer),
58 margin_right(integer),
59 width(integer),
60 text_align(oneof([justify, left]))
61 ]).
85html_text(Input) :-
86 html_text(Input, []).
87
88html_text(Input, Options) :-
89 ( xml_is_dom(Input)
90 -> DOM = Input
91 ; load_html(Input, DOM, Options)
92 ),
93 default_state(State0),
94 state_options(Options, State0, State),
95 init_nl,
96 format_dom(DOM, State).
97
98state_options([], State, State).
99state_options([H|T], State0, State) :-
100 H =.. [Key,Value],
101 ( fmt_option(Key, Type, _Default)
102 -> must_be(Type, Value),
103 State1 = State0.put(Key,Value)
104 ; State1 = State0
105 ),
106 state_options(T, State1, State).
107
108fmt_option(margin_left, integer, 0).
109fmt_option(margin_right, integer, 0).
110fmt_option(text_align, oneof([justify, left]), justify).
111fmt_option(width, between(10,1000), 72).
112
113default_state(State) :-
114 findall(Key-Value, fmt_option(Key, _, Value), Pairs),
115 dict_pairs(Dict, _, Pairs),
116 State = Dict.put(_{ style:[], list:[]}).
122format_dom([], _) :-
123 !.
124format_dom([H|T], State) :-
125 format_dom(H, State),
126 !,
127 format_dom(T, State).
128format_dom(Content, State) :-
129 Content = [H0|_],
130 \+ is_block_element(H0),
131 !,
132 ( append(Inline, [H|T], Content),
133 is_block_element(H)
134 -> true
135 ; Inline = Content
136 ),
137 format_dom(element(p, [], Inline), State),
138 format_dom([H|T], State).
139format_dom(element(html, _, Content), State) :-
140 !,
141 format_dom(Content, State).
142format_dom(element(head, _, _), _) :-
143 !.
144format_dom(element(body, _, Content), State) :-
145 !,
146 format_dom(Content, State).
147format_dom(element(E, Attrs, Content), State) :-
148 !,
149 ( format_element(E, Attrs, Content, State)
150 -> true
151 ; debug(format(html), 'Skipped block element ~q', [E])
152 ).
153
154format_element(pre, Attrs, [Content], State) :-
155 !,
156 block_element(pre, Attrs, Top-Bottom, BlockAttrs, Style),
157 update_style(Style, State, State1),
158 ask_nl(Top),
159 emit_code(Content, BlockAttrs, State1),
160 ask_nl(Bottom).
161format_element(table, Attrs, Content, State) :-
162 !,
163 block_element(table, Attrs, Top-Bottom, BlockAttrs, Style),
164 update_style(Style, State, State1),
165 state_par_properties(State1, BlockAttrs, BlockOptions),
166 ask_nl(Top),
167 emit_nl,
168 format_table(Content, Attrs, BlockOptions, State1),
169 ask_nl(Bottom).
170format_element(hr, Attrs, _, State) :-
171 !,
172 block_element(hr, Attrs, Top-Bottom, BlockAttrs, Style),
173 update_style(Style, State, State1),
174 state_par_properties(State1, BlockAttrs, BlockOptions),
175 ask_nl(Top),
176 emit_nl,
177 emit_hr(Attrs, BlockOptions, State1),
178 ask_nl(Bottom).
179format_element(Elem, Attrs, Content, State) :-
180 block_element(Elem, Attrs, Top-Bottom, BlockAttrs, Style),
181 !,
182 update_style(Style, State, State1),
183 block_words(Content, SubBlocks, Words, State1),
184 ( Words == []
185 -> true
186 ; ask_nl(Top),
187 emit_block(Words, BlockAttrs, State1),
188 ask_nl(Bottom)
189 ),
190 ( SubBlocks \== []
191 -> update_state_par_properties(BlockAttrs, State1, State2),
192 format_dom(SubBlocks, State2)
193 ; true
194 ).
195format_element(Elem, Attrs, Content, State) :-
196 list_element(Elem, Attrs, Top-Bottom, State, State1),
197 !,
198 open_list(Elem, State1, State2),
199 ask_nl(Top),
200 format_list(Content, Elem, 1, State2),
201 ask_nl(Bottom).
202format_element(Elem, Attrs, Content, State) :-
203 format_list_element(element(Elem, Attrs, Content), none, 0, State).
209block_element(El, Attrs, Margins, ParOptions, Style) :-
210 block_element(El, Margins0, ParOptions0, Style0),
211 ( nonvar(Attrs),
212 element_css(El, Attrs, CSS)
213 -> css_block_options(CSS, Margins0, Margins, ParOptions, Style1),
214 append(Style1, Style0, Style2),
215 list_to_set(Style2, Style)
216 ; Margins = Margins0,
217 ParOptions = ParOptions0,
218 Style = Style0
219 ).
220
221block_element(p, 1-2, [], []).
222block_element(div, 1-1, [], []).
223block_element(hr, 1-1, [], []).
224block_element(h1, 2-2, [], [bold]).
225block_element(h2, 2-2, [], [bold]).
226block_element(h3, 2-2, [], [bold]).
227block_element(h4, 2-2, [], [bold]).
228block_element(pre, 2-2, [], []).
229block_element(blockquote, 2-2, [margin_left(4), margin_right(4)], []).
230block_element(table, 2-2, [], []).
231
232list_element(ul, _, Margins, State0, State) :-
233 margins(4, 4, State0, State),
234 list_level_margins(State, Margins).
235list_element(ol, _, Margins, State0, State) :-
236 margins(4, 4, State0, State),
237 list_level_margins(State, Margins).
238list_element(dl, _, 2-2, State, State).
239
240list_element(ul).
241list_element(ol).
242list_element(dl).
243
244list_level_margins(State, 2-2) :-
245 nonvar(State),
246 State.get(list) == [],
247 !.
248list_level_margins(_, 0-0).
249
250format_list([], _, _, _).
251format_list([H|T], Type, Nth, State) :-
252 format_list_element(H, Type, Nth, State),
253 ( T == []
254 -> true
255 ; Nth1 is Nth + 1,
256 format_list(T, Type, Nth1, State)
257 ).
258
259format_list_element(element(LE, Attrs, Content), Type, Nth, State) :-
260 setup_list_element(LE, Attrs, Type, Nth, ListParProps, State, State1),
261 block_words(Content, Blocks, Words, State1),
262 emit_block(Words, ListParProps, State1),
263 ( Blocks \== []
264 -> ask_nl(2), 265 update_state_par_properties(ListParProps, State1, State2),
266 format_dom(Blocks, State2)
267 ; true
268 ).
269
270setup_list_element(li, _Attrs, _Type, Nth, ListParProps, State, State) :-
271 list_par_properties(State.list, Nth, ListParProps).
272setup_list_element(dt, _Attrs, _Type, _Nth, [], State, State2) :-
273 margins(0, 0, State, State1),
274 update_style([bold], State1, State2).
275setup_list_element(dd, _Attrs, _Type, _Nth, [], State, State1) :-
276 margins(4, 0, State, State1).
277
278list_item_element(li).
279list_item_element(dt).
280list_item_element(dd).
281
282list_par_properties([ul|_More], _, [bullet('\u2022')]).
283list_par_properties([ol|_More], N, [bullet(N)]).
290block_words(Content, RC, Words, State) :-
291 phrase(bwords(Content, RC, State), Words0),
292 join_whitespace(Words0, Words1),
293 trim_line(Words1, Words).
294
295bwords([], [], _) -->
296 !.
297bwords([H|T], Rest, _State) -->
298 { var(Rest),
299 is_block_element(H),
300 !,
301 Rest = [H|T]
302 }.
303bwords([H|T], Rest, State) -->
304 !,
305 bwordsel(H, State),
306 bwords(T, Rest, State).
307
308is_block_element(element(E,_,_)) :-
309 ( block_element(E, _, _, _)
310 ; list_element(E)
311 ; list_item_element(E)
312 ),
313 debug(format(html), 'Found block ~q', [E]),
314 !.
315
316bwordsel(element(Elem, Attrs, Content), State) -->
317 { styled_inline(Elem, Attrs, Margins, Style),
318 !,
319 update_style(Style, State, State1)
320 },
321 left_margin(Margins),
322 bwords(Content, [], State1),
323 right_margin(Margins).
324bwordsel(element(br, _, _), _State) -->
325 [br([])].
326bwordsel(CDATA, State) -->
327 { atomic(CDATA),
328 !,
329 split_string(CDATA, " \n\t\r", "", Words)
330 },
331 words(Words, State).
332bwordsel(element(Elem, _Attrs, _Content), _State) -->
333 { debug(format(html), 'Skipped inline element ~q', [Elem]) }.
334
335left_margin(0-_) --> !.
336left_margin(N-_) --> [b(N,_)].
337
338right_margin(_-0) --> !.
339right_margin(_-N) --> [b(N,_)].
340
341styled_inline(El, Attrs, Margins, Style) :-
342 styled_inline(El, Style0),
343 ( nonvar(Attrs),
344 element_css(El, Attrs, CSS)
345 -> css_inline_options(CSS, Margins, Style1),
346 append(Style1, Style0, Style2),
347 list_to_set(Style2, Style)
348 ; Style = Style0
349 ).
350
351styled_inline(b, [bold]).
352styled_inline(strong, [bold]).
353styled_inline(em, [bold]).
354styled_inline(span, []).
355styled_inline(i, [underline]).
356styled_inline(a, [underline]).
357styled_inline(var, []).
358styled_inline(code, []).
365words([], _) --> [].
366words([""|T0], State) -->
367 !,
368 { skip_leading_spaces(T0, T) },
369 space,
370 words(T, State).
371words([H|T], State) -->
372 word(H, State),
373 ( {T==[]}
374 -> []
375 ; { skip_leading_spaces(T, T1) },
376 space,
377 words(T1, State)
378 ).
379
380skip_leading_spaces([""|T0], T) :-
381 !,
382 skip_leading_spaces(T0, T).
383skip_leading_spaces(L, L).
384
385word(W, State) -->
386 { string_length(W, Len),
387 ( Style = State.get(style)
388 -> true
389 ; Style = []
390 )
391 },
392 [w(W, Len, Style)].
393
394space -->
395 [b(1,_)].
401join_whitespace([], []).
402join_whitespace([H0|T0], [H|T]) :-
403 join_whitespace(H0, H, T0, T1),
404 !,
405 join_whitespace(T1, T).
406join_whitespace([H|T0], [H|T]) :-
407 join_whitespace(T0, T).
408
409join_whitespace(b(Len0,_), b(Len,_), T0, T) :-
410 take_whitespace(T0, T, Len0, Len).
411
412take_whitespace([b(Len1,_)|T0], T, Len0, Len) :-
413 !,
414 Len2 is max(Len1,Len0),
415 take_whitespace(T0, T, Len2, Len).
416take_whitespace(L, L, Len, Len).
417
418
419
427update_style([], State, State) :-
428 !.
429update_style(Extra, State0, State) :-
430 ( get_dict(style, State0, Style0, State, Style)
431 -> add_style(Extra, Style0, Style)
432 ; add_style(Extra, [], Style),
433 put_dict(style, State0, Style, State)
434 ).
435
436add_style(Extra, Style0, Style) :-
437 reverse(Extra, RevExtra),
438 foldl(add1_style, RevExtra, Style0, Style).
444add1_style(New, Style0, Style) :-
445 ( style_overrides(New, Add, Overrides)
446 -> delete_all(Overrides, Style0, Style1),
447 append(Add, Style1, Style)
448 ; Style = [New|Style0]
449 ).
450
451delete_all([], List, List).
452delete_all([H|T], List0, List) :-
453 delete(List0, H, List1),
454 delete_all(T, List1, List).
455
456style_overrides(normal, [], [bold]).
457style_overrides(fg(C), [fg(C)], [fg(_), hfg(_)]).
458style_overrides(bg(C), [bg(C)], [bg(_), hbg(_)]).
459style_overrides(underline(false), [], [underline]).
460
461margins(Left, Right, State0, State) :-
462 _{ margin_left:ML0, margin_right:MR0 } >:< State0,
463 ML is ML0 + Left,
464 MR is MR0 + Right,
465 State = State0.put(_{margin_left:ML, margin_right:MR}).
466
467open_list(Type, State0, State) :-
468 get_dict(list, State0, Lists, State, [Type|Lists]).
469
470update_state_par_properties([], State, State).
471update_state_par_properties([H|T], State0, State) :-
472 H =.. [ Key, Value ],
473 State1 = State0.put(Key,Value),
474 update_state_par_properties(T, State1, State).
481state_par_properties(State, Props) :-
482 Props0 = [ margin_left(LM),
483 margin_right(RM),
484 text_align(TA),
485 width(W),
486 pad(Pad)
487 ],
488 _{margin_left:LM, margin_right:RM, text_align:TA, width:W,
489 pad:Pad} >:< State,
490 filled_par_props(Props0, Props).
491
492filled_par_props([], []).
493filled_par_props([H|T0], [H|T]) :-
494 arg(1, H, A),
495 nonvar(A),
496 !,
497 filled_par_props(T0, T).
498filled_par_props([_|T0], T) :-
499 filled_par_props(T0, T).
500
501
502state_par_properties(State, Options, BlockOptions) :-
503 state_par_properties(State, Options0),
504 foldl(merge_par_option, Options, Options0, BlockOptions).
505
506merge_par_option(margin_left(ML0), Options0, [margin_left(ML)|Options1]) :-
507 !,
508 select_option(margin_left(ML1), Options0, Options1, 0),
509 ML is ML0+ML1.
510merge_par_option(margin_right(MR0), Options0, [margin_right(MR)|Options1]) :-
511 !,
512 select_option(margin_right(MR1), Options0, Options1, 0),
513 MR is MR0+MR1.
514merge_par_option(Opt, Options0, Options) :-
515 merge_options([Opt], Options0, Options).
523emit_block([], _, _) :-
524 !.
525emit_block(Words, Options, State) :-
526 state_par_properties(State, Options, BlockOptions),
527 use_current_position(BlockOptions, BlockOptions1),
528 ask_nl(1),
529 emit_nl,
530 format_paragraph(Words, BlockOptions1),
531 ask_nl(1).
532
533use_current_position(Options0, Options) :-
534 nb_current(nl_pending, start),
535 line_position(current_output, Pos),
536 Pos > 0,
537 !,
538 Hang is -Pos,
539 Options = [hang(Hang)|Options0].
540use_current_position(Options, Options).
549init_nl :-
550 nb_setval(nl_pending, start).
551
552init_nl(Old) :-
553 ( nb_current(nl_pending, Old)
554 -> true
555 ; Old = []
556 ),
557 nb_setval(nl_pending, start).
558exit_nl(Old) :-
559 nb_setval(nl_pending, Old).
560
561ask_nl(N) :-
562 ( nb_current(nl_pending, N0)
563 -> ( N0 == start
564 -> true
565 ; integer(N0)
566 -> N1 is max(N0, N),
567 nb_setval(nl_pending, N1)
568 ; nb_setval(nl_pending, N)
569 )
570 ; nb_setval(nl_pending, N)
571 ).
572
573emit_nl :-
574 ( nb_current(nl_pending, N),
575 integer(N)
576 -> forall(between(1,N,_), nl)
577 ; true
578 ),
579 nb_setval(nl_pending, 0).
580
581
582
588emit_code(Content, BlockAttrs, State) :-
589 Style = State.style,
590 split_string(Content, "\n", "", Lines),
591 option(margin_left(LM0), BlockAttrs, 4),
592 LM is LM0+State.margin_left,
593 ask_nl(1),
594 emit_nl,
595 emit_code_lines(Lines, 1, LM, Style),
596 ask_nl(1).
597
598emit_code_lines([], _, _, _).
599emit_code_lines([H|T], LineNo, LM, Style) :-
600 emit_code_line(H, LineNo, LM, Style),
601 LineNo1 is LineNo + 1,
602 emit_code_lines(T, LineNo1, LM, Style).
603
604emit_code_line(Line, _LineNo, LM, Style) :-
605 emit_nl,
606 emit_indent(LM),
607 ( Style == []
608 -> write(Line)
609 ; ansi_format(Style, '~s', [Line])
610 ),
611 ask_nl(1).
612
613emit_indent(N) :-
614 forall(between(1, N, _),
615 put_char(' ')).
616
617
618
624format_table(Content, Attrs, BlockAttrs, State) :-
625 tty_state(TTY),
626 option(margin_left(ML), BlockAttrs, 0),
627 option(margin_right(MR), BlockAttrs, 0),
628 MaxTableWidth is State.width - ML - MR,
629 table_cell_state(Attrs, State, CellState0),
630 CellState = CellState0.put(tty, TTY),
631 phrase(rows(Content), Rows),
632 columns(Rows, Columns),
633 maplist(auto_column_width(CellState), Columns, Widths),
634 column_widths(Widths, MaxTableWidth, ColWidths),
635 maplist(format_row(ColWidths, CellState, ML), Rows).
636
637tty_state(TTY) :-
638 stream_property(current_output, tty(true)),
639 !,
640 TTY = true.
641tty_state(false).
649column_widths(Widths, MaxTableWidth, Widths) :-
650 sum_list(Widths, AutoWidth),
651 AutoWidth =< MaxTableWidth,
652 !.
653column_widths(AutoWidths, MaxTableWidth, Widths) :-
654 sort(0, >=, AutoWidths, Sorted),
655 append(Wrapped, Keep, Sorted),
656 sum_list(Keep, KeepWidth),
657 KeepWidth < MaxTableWidth/2,
658 length(Wrapped, NWrapped),
659 WideWidth is round((MaxTableWidth-KeepWidth)/NWrapped),
660 ( [KeepW|_] = Keep
661 -> true
662 ; KeepW = 0
663 ),
664 !,
665 maplist(truncate_column(KeepW,WideWidth), AutoWidths, Widths).
666
667truncate_column(Keep, WideWidth, AutoWidth, Width) :-
668 ( AutoWidth =< Keep
669 -> Width = AutoWidth
670 ; Width = WideWidth
671 ).
672
673table_cell_state(Attrs, State, CellState) :-
674 ( element_css(table, Attrs, CSS)
675 -> true
676 ; CSS = []
677 ),
678 option(padding_left(PL), CSS, 1),
679 option(padding_right(PR), CSS, 1),
680 CellState = State.put(_{margin_left:PL, margin_right:PR}).
685rows([]) --> [].
686rows([H|T]) --> rows(H), rows(T).
687rows([element(tbody,_,Content)|T]) --> rows(Content), rows(T).
688rows([element(tr,Attrs,Columns)|T]) --> [row(Columns, Attrs)], rows(T).
695columns(Rows, Columns) :-
696 columns(Rows, 1, Columns).
697
698columns(Rows, I, Columns) :-
699 maplist(row_column(I, Found), Rows, H),
700 ( Found == true
701 -> Columns = [H|T],
702 I2 is I + 1,
703 columns(Rows, I2, T)
704 ; Columns = []
705 ).
706
707row_column(I, Found, row(Columns, _Attrs), Cell) :-
708 ( nth1(I, Columns, Cell)
709 -> Found = true
710 ; Cell = element(td,[],[])
711 ).
712
713auto_column_width(State, Col, Width) :-
714 maplist(auto_cell_width(State), Col, Widths),
715 max_list(Widths, Width).
716
717auto_cell_width(State, Cell, Width) :-
718 cell_colspan(Cell, 1),
719 !,
720 format_cell_to_string(Cell, 1_000, State, String),
721 write_size(String, Width0, _Height, []),
722 Width is Width0 + State.margin_right.
723auto_cell_width(_, _, 0).
729format_row(ColWidths, State, MarginLeft, Row) :-
730 hrule(Row, ColWidths, MarginLeft),
731 format_cells(ColWidths, CWSpanned, 1, Row, State, Cells),
732 format_row_lines(1, CWSpanned, Cells, MarginLeft).
733
734hrule(row(_, Attrs), ColWidths, MarginLeft) :-
735 attrs_classes(Attrs, Classes),
736 memberchk(hline, Classes),
737 !,
738 sum_list(ColWidths, RuleLen),
739 format('~N~t~*|~`-t~*+', [MarginLeft, RuleLen]).
740hrule(_, _, _).
741
742format_row_lines(LineNo, Widths, Cells, MarginLeft) :-
743 nth_row_line(Widths, 1, LineNo, Cells, CellLines, Found),
744 ( Found == true
745 -> emit_nl,
746 emit_indent(MarginLeft),
747 maplist(emit_cell_line, CellLines),
748 ask_nl(1),
749 LineNo1 is LineNo + 1,
750 format_row_lines(LineNo1, Widths, Cells, MarginLeft)
751 ; true
752 ).
753
754emit_cell_line(Line-Pad) :-
755 write(Line),
756 forall(between(1,Pad,_), put_char(' ')).
757
758nth_row_line([], _, _, _, [], _).
759nth_row_line([ColW|CWT], CellNo, LineNo, Cells, [CellLine-Pad|ColLines],
760 Found) :-
761 nth1(CellNo, Cells, CellLines),
762 ( nth1(LineNo, CellLines, CellLine)
763 -> Found = true,
764 Pad = 0
765 ; CellLine = '', Pad = ColW
766 ),
767 CellNo1 is CellNo + 1,
768 nth_row_line(CWT, CellNo1, LineNo, Cells, ColLines, Found).
777format_cells([], [], _, _, _, []) :- !.
778format_cells(CWidths, [HW|TW], Column, Row, State, [HC|TC]) :-
779 Row = row(Columns, _Attrs),
780 nth1(Column, Columns, Cell),
781 cell_colspan(Cell, CWidths, HW, TW0),
782 cell_align(Cell, Align),
783 format_cell_to_string(Cell, HW, State.put(_{pad:' ', text_align:Align}), String),
784 split_string(String, "\n", "", HC),
785 Column1 is Column+1,
786 format_cells(TW0, TW, Column1, Row, State, TC).
787
788cell_colspan(Cell, CWidths, HW, TW) :-
789 cell_colspan(Cell, Span),
790 length(SpanW, Span),
791 append(SpanW, TW, CWidths),
792 sum_list(SpanW, HW).
793
794cell_colspan(element(_,Attrs,_), Span) :-
795 ( memberchk(colspan=SpanA, Attrs),
796 atom_number(SpanA, SpanN)
797 -> Span = SpanN
798 ; Span = 1
799 ).
807cell_align(element(_,Attrs,_), Align) :-
808 ( memberchk(align=AlignA, Attrs)
809 -> Align = AlignA
810 ; memberchk(style=Style, Attrs),
811 style_css_attrs(Style, Props),
812 memberchk('text-align'(AlignA), Props)
813 -> Align = AlignA
814 ; Align = left
815 ).
822format_cell_to_string(element(_,_,[]), ColWidth, State, String) :-
823 Pad = State.get(pad),
824 !,
825 length(Chars, ColWidth),
826 maplist(=(Pad), Chars),
827 atomics_to_string(Chars, String).
828format_cell_to_string(Cell, ColWidth, State, String) :-
829 setup_call_cleanup(
830 init_nl(NlState),
831 with_output_to(
832 string(String),
833 format_cell(Cell, ColWidth, State)),
834 exit_nl(NlState)).
835
836format_cell(element(E, _Attrs, Content), ColWidth, State) :-
837 set_stream(current_output, tty(State.tty)),
838 cell_element(E, Style),
839 update_style(Style, State.put(width, ColWidth), CellState),
840 block_words(Content, Blocks, Words, CellState),
841 emit_block(Words, [], CellState),
842 ( Blocks \== []
843 -> format_dom(Blocks, CellState)
844 ; true
845 ).
846
847cell_element(td, [normal]).
848cell_element(th, [bold]).
855emit_hr(_Attrs, BlockAttrs, State) :-
856 option(margin_left(ML), BlockAttrs, 0),
857 option(margin_right(MR), BlockAttrs, 0),
858 RuleWidth is State.width - ML - MR,
859 Style = State.style,
860 emit_indent(ML),
861 ( Style == []
862 -> format('~|~*t~*+', [0'-, RuleWidth])
863 ; ansi_format(Style, '~|~*t~*+', [0'-, RuleWidth])
864 )
Render HTML as plain text
This module renders HTML markup as plain text, just like the open lynx program does. It is (as yet), limited to and spacialized for dealing with the SWI-Prolog documentation. This library first of all supports help/1.