Aion-Format

 view release on metacpan or  search on metacpan

lib/Aion/Format/Html.pm  view on Meta::CPAN

	# Выбрасываем из стека предыдущий тег
	my @ret;
	push @ret, pop @$S while @$S and
		($TOP_NEW_TAG{ $S->[$#$S][0] })->{$tag};

	push @$S, [$tag, $atag] unless exists $SINGLE_TAG{$tag};

	@ret
}

# Выбрасывает тег из стека. Возвращает выдавленные им и выбрасывает исключение, если такого в стеке нет
sub out_tag(\@$) {
	my ($S, $tag) = @_;

	# Это одиночный тег - он не может быть закрывающим
	die "</$tag> is a single tag - it cannot be a closing tag" if exists $SINGLE_TAG{$tag};

	# закрываем предыдущий, если нужно
	my @ret;
	push @ret, pop @$S while @$S and
		($TOP_CLOSE_TAG{$S->[$#$S][0]})->{$tag};

	die "</$tag>, but stack is empty!" unless @$S;

	# тег равен закрывающему
	die "<$S->[$#$S][0]> in stack ne </$tag>!" if $S->[$#$S][0] ne $tag;

	push @ret, pop @$S;

	@ret
}


# Разбивает текст на страницы с учётом html-тегов
#
# 1. html-тег должен быть так же разнесён на страницы.
#
sub split_on_pages(@) {
	my ($html, $symbols_on_page, $by) = @_;

	# На какое расстояние страница может быть больше
	$by //= $symbols_on_page / 3 < 1000 ? int($symbols_on_page / 3): 1000;
	my $max = $symbols_on_page + $by;

	my @pages;		# массив страниц
	my @page;		# массив элементов текста и тегов текущей страницы
	my $c = 0;		# количество символов в текущей странице
	my $i_page = 0;	# индекс элемента @page который привысил размер страницы
	my $c_page = 0;	# количество символов @page по $i_page
	my $is_proposal = 0;			# В конце текущей страницы обнаружен конец предложения
	my $re_proposal = qr/[.?!…]/;
	my @S;			# массив открывающих тегов [tag, '<tag ...>']

	# Функция фиксирует страницу и сбрасывает счётчики
	my $make_page = sub {
		push @pages, join "", @page, map { "</$_->[0]>" } reverse @S;
		$i_page = $c = $is_proposal = 0;
		@page = map $_->[1], @S;
	};
	
	for(grep length, split m{(
		<[a-z] [^<>]* >
		| </ \s* [a-z]\w* \s* >
		| &(?: [a-z]\w* | \# \d+ | \#x[0-9a-f]+ ) ;?
		| \n 		# Абзац
		| $re_proposal+	# Предложение
		| \b		# Слово
	)}xiu, $html) {

		if(/^&/) {$c++} 	# html-символ
		elsif(/^<\/\s*([a-z]\w*)/) { # закрывающий тег
			my $tag = lc $1;
			eval { out_tag @S, $tag };
			next if $@;
			# </p> превращаем в <p></p>
			$_ = "<p></p>" if $tag eq "p";
		}
		elsif(/^<([a-z]\w*)/) { in_tag @S, lc $1, $_ }	# тег
		else {$c += length}	# текст

		push @page, $_; # накапливаем символы в массиве @page

		next if $c < $symbols_on_page;	# страницу не набрали - тогда на next

		$c_page = $c, $i_page = @page if !$i_page;

		# Просматриваем вперёд пока не найдём границу или не достигнем ограничения
		if(/^\n/) { $make_page->() } 	# Абзац
		elsif(!$is_proposal && /^$re_proposal/) { $i_page = @page; $c_page = $c; $is_proposal = 1 }
		elsif($c >= $max) {
			# Если следующий за предложением или словом элемент - пробелы, то добавляем их к странице
			$c_page -= length $page[$i_page++] if $page[$i_page] =~ /^\s/;
			my @x = splice @page, $i_page;
			$make_page->();
			push @page, @x;
			$c -= $c_page;
		}
	}

	$make_page->() if @page;

	# Если размер последней страницы меньше чем 2/3, то добавляем её к предпоследней
	$pages[$#pages - 1] .= pop @pages if @pages > 1 and length($pages[$#pages]) < $symbols_on_page * 2 / 3;

	#my $len = 0; $len += length for @pages;
	#die "Суммарный размер страниц не изменился: " . length($html) . " == $len pages=" . @pages . " ->\n\n$html" if $len == length $html;

	my ($end1) = $html =~ m!([^<>\s]{1,13})\s*(</?\w[^<>]*>\s*)*$!a;
	my ($end2) = $pages[$#pages] =~ m!([^<>\s]{1,13})\s*(</?\w[^<>]*>\s*)*$!a;
	die "Концы текста и последней страницы не сходятся! `$end1` <> `$end2`" if $end1 ne $end2;

	return @pages;
}

our %TAG2SPACE = (
	"br" => "\n",
	"dd" => "\n  ",
	"table" => "\n",
	"tr" => "\n| ",
	"td" => "\t| ",
	"th" => "\t| ",



( run in 3.418 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )