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 block_list_context(State.get(list)),
247 !.
248list_level_margins(_, 0-0).
258block_list_context([]).
259block_list_context([dl|_]).
260
261format_list([], _, _, _).
262format_list([H|T], Type, Nth, State) :-
263 format_list_element(H, Type, Nth, State),
264 ( T == []
265 -> true
266 ; Nth1 is Nth + 1,
267 format_list(T, Type, Nth1, State)
268 ).
269
270format_list_element(element(LE, Attrs, Content), Type, Nth, State) :-
271 setup_list_element(LE, Attrs, Type, Nth, ListParProps, State, State1),
272 block_words(Content, Blocks, Words, State1),
273 emit_block(Words, ListParProps, State1),
274 ( Blocks \== []
275 -> ask_nl(2), 276 update_state_par_properties(ListParProps, State1, State2),
277 format_dom(Blocks, State2)
278 ; true
279 ).
280
281setup_list_element(li, _Attrs, _Type, Nth, ListParProps, State, State) :-
282 list_par_properties(State.list, Nth, ListParProps).
283setup_list_element(dt, _Attrs, _Type, _Nth, [], State, State2) :-
284 margins(0, 0, State, State1),
285 update_style([bold], State1, State2).
286setup_list_element(dd, _Attrs, _Type, _Nth, [], State, State1) :-
287 margins(4, 0, State, State1).
288
289list_item_element(li).
290list_item_element(dt).
291list_item_element(dd).
292
293list_par_properties([ul|_More], _, [bullet('\u2022')]).
294list_par_properties([ol|_More], N, [bullet(N)]).
301block_words(Content, RC, Words, State) :-
302 phrase(bwords(Content, RC, State), Words0),
303 join_whitespace(Words0, Words1),
304 trim_line(Words1, Words).
305
306bwords([], [], _) -->
307 !.
308bwords([H|T], Rest, _State) -->
309 { var(Rest),
310 is_block_element(H),
311 !,
312 Rest = [H|T]
313 }.
314bwords([H|T], Rest, State) -->
315 !,
316 bwordsel(H, State),
317 bwords(T, Rest, State).
318
319is_block_element(element(E,_,_)) :-
320 ( block_element(E, _, _, _)
321 ; list_element(E)
322 ; list_item_element(E)
323 ),
324 debug(format(html), 'Found block ~q', [E]),
325 !.
326
327bwordsel(element(Elem, Attrs, Content), State) -->
328 { styled_inline(Elem, Attrs, Margins, Style),
329 !,
330 update_style(Style, State, State1)
331 },
332 left_margin(Margins),
333 bwords(Content, [], State1),
334 right_margin(Margins).
335bwordsel(element(br, _, _), _State) -->
336 [br([])].
337bwordsel(CDATA, State) -->
338 { atomic(CDATA),
339 !,
340 split_string(CDATA, " \n\t\r", "", Words)
341 },
342 words(Words, State).
343bwordsel(element(Elem, _Attrs, _Content), _State) -->
344 { debug(format(html), 'Skipped inline element ~q', [Elem]) }.
345
346left_margin(0-_) --> !.
347left_margin(N-_) --> [b(N,_)].
348
349right_margin(_-0) --> !.
350right_margin(_-N) --> [b(N,_)].
351
352styled_inline(El, Attrs, Margins, Style) :-
353 styled_inline(El, Style0),
354 ( nonvar(Attrs),
355 element_css(El, Attrs, CSS)
356 -> css_inline_options(CSS, Margins, Style1),
357 append(Style1, Style0, Style2),
358 list_to_set(Style2, Style)
359 ; Style = Style0
360 ).
361
362styled_inline(b, [bold]).
363styled_inline(strong, [bold]).
364styled_inline(em, [bold]).
365styled_inline(span, []).
366styled_inline(i, [underline]).
367styled_inline(a, [underline]).
368styled_inline(var, []).
369styled_inline(code, []).
376words([], _) --> [].
377words([""|T0], State) -->
378 !,
379 { skip_leading_spaces(T0, T) },
380 space,
381 words(T, State).
382words([H|T], State) -->
383 word(H, State),
384 ( {T==[]}
385 -> []
386 ; { skip_leading_spaces(T, T1) },
387 space,
388 words(T1, State)
389 ).
390
391skip_leading_spaces([""|T0], T) :-
392 !,
393 skip_leading_spaces(T0, T).
394skip_leading_spaces(L, L).
395
396word(W, State) -->
397 { string_length(W, Len),
398 ( Style = State.get(style)
399 -> true
400 ; Style = []
401 )
402 },
403 [w(W, Len, Style)].
404
405space -->
406 [b(1,_)].
412join_whitespace([], []).
413join_whitespace([H0|T0], [H|T]) :-
414 join_whitespace(H0, H, T0, T1),
415 !,
416 join_whitespace(T1, T).
417join_whitespace([H|T0], [H|T]) :-
418 join_whitespace(T0, T).
419
420join_whitespace(b(Len0,_), b(Len,_), T0, T) :-
421 take_whitespace(T0, T, Len0, Len).
422
423take_whitespace([b(Len1,_)|T0], T, Len0, Len) :-
424 !,
425 Len2 is max(Len1,Len0),
426 take_whitespace(T0, T, Len2, Len).
427take_whitespace(L, L, Len, Len).
428
429
430
438update_style([], State, State) :-
439 !.
440update_style(Extra, State0, State) :-
441 ( get_dict(style, State0, Style0, State, Style)
442 -> add_style(Extra, Style0, Style)
443 ; add_style(Extra, [], Style),
444 put_dict(style, State0, Style, State)
445 ).
446
447add_style(Extra, Style0, Style) :-
448 reverse(Extra, RevExtra),
449 foldl(add1_style, RevExtra, Style0, Style).
455add1_style(New, Style0, Style) :-
456 ( style_overrides(New, Add, Overrides)
457 -> delete_all(Overrides, Style0, Style1),
458 append(Add, Style1, Style)
459 ; Style = [New|Style0]
460 ).
461
462delete_all([], List, List).
463delete_all([H|T], List0, List) :-
464 delete(List0, H, List1),
465 delete_all(T, List1, List).
466
467style_overrides(normal, [], [bold]).
468style_overrides(fg(C), [fg(C)], [fg(_), hfg(_)]).
469style_overrides(bg(C), [bg(C)], [bg(_), hbg(_)]).
470style_overrides(underline(false), [], [underline]).
471
472margins(Left, Right, State0, State) :-
473 _{ margin_left:ML0, margin_right:MR0 } >:< State0,
474 ML is ML0 + Left,
475 MR is MR0 + Right,
476 State = State0.put(_{margin_left:ML, margin_right:MR}).
477
478open_list(Type, State0, State) :-
479 get_dict(list, State0, Lists, State, [Type|Lists]).
480
481update_state_par_properties([], State, State).
482update_state_par_properties([H|T], State0, State) :-
483 H =.. [ Key, Value ],
484 State1 = State0.put(Key,Value),
485 update_state_par_properties(T, State1, State).
492state_par_properties(State, Props) :-
493 Props0 = [ margin_left(LM),
494 margin_right(RM),
495 text_align(TA),
496 width(W),
497 pad(Pad)
498 ],
499 _{margin_left:LM, margin_right:RM, text_align:TA, width:W,
500 pad:Pad} >:< State,
501 filled_par_props(Props0, Props).
502
503filled_par_props([], []).
504filled_par_props([H|T0], [H|T]) :-
505 arg(1, H, A),
506 nonvar(A),
507 !,
508 filled_par_props(T0, T).
509filled_par_props([_|T0], T) :-
510 filled_par_props(T0, T).
511
512
513state_par_properties(State, Options, BlockOptions) :-
514 state_par_properties(State, Options0),
515 foldl(merge_par_option, Options, Options0, BlockOptions).
516
517merge_par_option(margin_left(ML0), Options0, [margin_left(ML)|Options1]) :-
518 !,
519 select_option(margin_left(ML1), Options0, Options1, 0),
520 ML is ML0+ML1.
521merge_par_option(margin_right(MR0), Options0, [margin_right(MR)|Options1]) :-
522 !,
523 select_option(margin_right(MR1), Options0, Options1, 0),
524 MR is MR0+MR1.
525merge_par_option(Opt, Options0, Options) :-
526 merge_options([Opt], Options0, Options).
534emit_block([], _, _) :-
535 !.
536emit_block(Words, Options, State) :-
537 state_par_properties(State, Options, BlockOptions),
538 use_current_position(BlockOptions, BlockOptions1),
539 ask_nl(1),
540 emit_nl,
541 format_paragraph(Words, BlockOptions1),
542 ask_nl(1).
543
544use_current_position(Options0, Options) :-
545 nb_current(nl_pending, start),
546 line_position(current_output, Pos),
547 Pos > 0,
548 !,
549 Hang is -Pos,
550 Options = [hang(Hang)|Options0].
551use_current_position(Options, Options).
560init_nl :-
561 nb_setval(nl_pending, start).
562
563init_nl(Old) :-
564 ( nb_current(nl_pending, Old)
565 -> true
566 ; Old = []
567 ),
568 nb_setval(nl_pending, start).
569exit_nl(Old) :-
570 nb_setval(nl_pending, Old).
571
572ask_nl(N) :-
573 ( nb_current(nl_pending, N0)
574 -> ( N0 == start
575 -> true
576 ; integer(N0)
577 -> N1 is max(N0, N),
578 nb_setval(nl_pending, N1)
579 ; nb_setval(nl_pending, N)
580 )
581 ; nb_setval(nl_pending, N)
582 ).
583
584emit_nl :-
585 ( nb_current(nl_pending, N),
586 integer(N)
587 -> forall(between(1,N,_), nl)
588 ; true
589 ),
590 nb_setval(nl_pending, 0).
591
592
593
599emit_code(Content, BlockAttrs, State) :-
600 Style = State.style,
601 split_string(Content, "\n", "", Lines),
602 option(margin_left(LM0), BlockAttrs, 4),
603 LM is LM0+State.margin_left,
604 ask_nl(1),
605 emit_nl,
606 emit_code_lines(Lines, 1, LM, Style),
607 ask_nl(1).
608
609emit_code_lines([], _, _, _).
610emit_code_lines([H|T], LineNo, LM, Style) :-
611 emit_code_line(H, LineNo, LM, Style),
612 LineNo1 is LineNo + 1,
613 emit_code_lines(T, LineNo1, LM, Style).
614
615emit_code_line(Line, _LineNo, LM, Style) :-
616 emit_nl,
617 emit_indent(LM),
618 ( Style == []
619 -> write(Line)
620 ; ansi_format(Style, '~s', [Line])
621 ),
622 ask_nl(1).
623
624emit_indent(N) :-
625 forall(between(1, N, _),
626 put_char(' ')).
627
628
629
635format_table(Content, Attrs, BlockAttrs, State) :-
636 tty_state(TTY),
637 option(margin_left(ML), BlockAttrs, 0),
638 option(margin_right(MR), BlockAttrs, 0),
639 MaxTableWidth is State.width - ML - MR,
640 table_cell_state(Attrs, State, CellState0),
641 CellState = CellState0.put(tty, TTY),
642 phrase(rows(Content), Rows),
643 columns(Rows, Columns),
644 maplist(auto_column_width(CellState), Columns, Widths),
645 column_widths(Widths, MaxTableWidth, ColWidths),
646 maplist(format_row(ColWidths, CellState, ML), Rows).
647
648tty_state(TTY) :-
649 stream_property(current_output, tty(true)),
650 !,
651 TTY = true.
652tty_state(false).
660column_widths(Widths, MaxTableWidth, Widths) :-
661 sum_list(Widths, AutoWidth),
662 AutoWidth =< MaxTableWidth,
663 !.
664column_widths(AutoWidths, MaxTableWidth, Widths) :-
665 sort(0, >=, AutoWidths, Sorted),
666 append(Wrapped, Keep, Sorted),
667 sum_list(Keep, KeepWidth),
668 KeepWidth < MaxTableWidth/2,
669 length(Wrapped, NWrapped),
670 WideWidth is round((MaxTableWidth-KeepWidth)/NWrapped),
671 ( [KeepW|_] = Keep
672 -> true
673 ; KeepW = 0
674 ),
675 !,
676 maplist(truncate_column(KeepW,WideWidth), AutoWidths, Widths).
677
678truncate_column(Keep, WideWidth, AutoWidth, Width) :-
679 ( AutoWidth =< Keep
680 -> Width = AutoWidth
681 ; Width = WideWidth
682 ).
683
684table_cell_state(Attrs, State, CellState) :-
685 ( element_css(table, Attrs, CSS)
686 -> true
687 ; CSS = []
688 ),
689 option(padding_left(PL), CSS, 1),
690 option(padding_right(PR), CSS, 1),
691 CellState = State.put(_{margin_left:PL, margin_right:PR}).
696rows([]) --> [].
697rows([H|T]) --> rows(H), rows(T).
698rows([element(tbody,_,Content)|T]) --> rows(Content), rows(T).
699rows([element(tr,Attrs,Columns)|T]) --> [row(Columns, Attrs)], rows(T).
706columns(Rows, Columns) :-
707 columns(Rows, 1, Columns).
708
709columns(Rows, I, Columns) :-
710 maplist(row_column(I, Found), Rows, H),
711 ( Found == true
712 -> Columns = [H|T],
713 I2 is I + 1,
714 columns(Rows, I2, T)
715 ; Columns = []
716 ).
717
718row_column(I, Found, row(Columns, _Attrs), Cell) :-
719 ( nth1(I, Columns, Cell)
720 -> Found = true
721 ; Cell = element(td,[],[])
722 ).
723
724auto_column_width(State, Col, Width) :-
725 maplist(auto_cell_width(State), Col, Widths),
726 max_list(Widths, Width).
727
728auto_cell_width(State, Cell, Width) :-
729 cell_colspan(Cell, 1),
730 !,
731 format_cell_to_string(Cell, 1_000, State, String),
732 write_size(String, Width0, _Height, []),
733 Width is Width0 + State.margin_right.
734auto_cell_width(_, _, 0).
740format_row(ColWidths, State, MarginLeft, Row) :-
741 hrule(Row, ColWidths, MarginLeft),
742 format_cells(ColWidths, CWSpanned, 1, Row, State, Cells),
743 format_row_lines(1, CWSpanned, Cells, MarginLeft).
744
745hrule(row(_, Attrs), ColWidths, MarginLeft) :-
746 attrs_classes(Attrs, Classes),
747 memberchk(hline, Classes),
748 !,
749 sum_list(ColWidths, RuleLen),
750 format('~N~t~*|~`-t~*+', [MarginLeft, RuleLen]).
751hrule(_, _, _).
752
753format_row_lines(LineNo, Widths, Cells, MarginLeft) :-
754 nth_row_line(Widths, 1, LineNo, Cells, CellLines, Found),
755 ( Found == true
756 -> emit_nl,
757 emit_indent(MarginLeft),
758 maplist(emit_cell_line, CellLines),
759 ask_nl(1),
760 LineNo1 is LineNo + 1,
761 format_row_lines(LineNo1, Widths, Cells, MarginLeft)
762 ; true
763 ).
764
765emit_cell_line(Line-Pad) :-
766 write(Line),
767 forall(between(1,Pad,_), put_char(' ')).
768
769nth_row_line([], _, _, _, [], _).
770nth_row_line([ColW|CWT], CellNo, LineNo, Cells, [CellLine-Pad|ColLines],
771 Found) :-
772 nth1(CellNo, Cells, CellLines),
773 ( nth1(LineNo, CellLines, CellLine)
774 -> Found = true,
775 Pad = 0
776 ; CellLine = '', Pad = ColW
777 ),
778 CellNo1 is CellNo + 1,
779 nth_row_line(CWT, CellNo1, LineNo, Cells, ColLines, Found).
788format_cells([], [], _, _, _, []) :- !.
789format_cells(CWidths, [HW|TW], Column, Row, State, [HC|TC]) :-
790 Row = row(Columns, _Attrs),
791 nth1(Column, Columns, Cell),
792 cell_colspan(Cell, CWidths, HW, TW0),
793 cell_align(Cell, Align),
794 format_cell_to_string(Cell, HW, State.put(_{pad:' ', text_align:Align}), String),
795 split_string(String, "\n", "", HC),
796 Column1 is Column+1,
797 format_cells(TW0, TW, Column1, Row, State, TC).
798
799cell_colspan(Cell, CWidths, HW, TW) :-
800 cell_colspan(Cell, Span),
801 length(SpanW, Span),
802 append(SpanW, TW, CWidths),
803 sum_list(SpanW, HW).
804
805cell_colspan(element(_,Attrs,_), Span) :-
806 ( memberchk(colspan=SpanA, Attrs),
807 atom_number(SpanA, SpanN)
808 -> Span = SpanN
809 ; Span = 1
810 ).
818cell_align(element(_,Attrs,_), Align) :-
819 ( memberchk(align=AlignA, Attrs)
820 -> Align = AlignA
821 ; memberchk(style=Style, Attrs),
822 style_css_attrs(Style, Props),
823 memberchk('text-align'(AlignA), Props)
824 -> Align = AlignA
825 ; Align = left
826 ).
833format_cell_to_string(element(_,_,[]), ColWidth, State, String) :-
834 Pad = State.get(pad),
835 !,
836 length(Chars, ColWidth),
837 maplist(=(Pad), Chars),
838 atomics_to_string(Chars, String).
839format_cell_to_string(Cell, ColWidth, State, String) :-
840 setup_call_cleanup(
841 init_nl(NlState),
842 with_output_to(
843 string(String),
844 format_cell(Cell, ColWidth, State)),
845 exit_nl(NlState)).
846
847format_cell(element(E, _Attrs, Content), ColWidth, State) :-
848 set_stream(current_output, tty(State.tty)),
849 cell_element(E, Style),
850 update_style(Style, State.put(width, ColWidth), CellState),
851 block_words(Content, Blocks, Words, CellState),
852 emit_block(Words, [], CellState),
853 ( Blocks \== []
854 -> format_dom(Blocks, CellState)
855 ; true
856 ).
857
858cell_element(td, [normal]).
859cell_element(th, [bold]).
866emit_hr(_Attrs, BlockAttrs, State) :-
867 option(margin_left(ML), BlockAttrs, 0),
868 option(margin_right(MR), BlockAttrs, 0),
869 RuleWidth is State.width - ML - MR,
870 Style = State.style,
871 emit_indent(ML),
872 ( Style == []
873 -> format('~|~*t~*+', [0'-, RuleWidth])
874 ; ansi_format(Style, '~|~*t~*+', [0'-, RuleWidth])
875 )
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.