our
@EXPORT
=
qw(pxml_xhtml_print)
;
our
@EXPORT_OK
=
qw(pxml_print
pxml_print_fragment
pxml_xhtml_print_fast
pxml_print_fragment_fast
putxmlfile
puthtmlfile
attribute_escape
content_escape
)
;
our
%EXPORT_TAGS
= (
all
=> [
@EXPORT
,
@EXPORT_OK
]);
use
PXML
qw(is_pxml_element is_pxmlflush)
;
sub
is_somearray {
@_
== 1 or fp_croak_arity 1;
my
$r
=
ref
(
$_
[0]);
$r
eq
"ARRAY"
or
$r
eq
"PXML::Body"
}
sub
is_empty_string {
@_
== 1 or fp_croak_arity 1;
defined
$_
[0] and !
length
ref
$_
[0] and
$_
[0] eq
""
}
my
%attribute_escape
= (
'&'
=>
'&'
,
'<'
=>
'<'
,
'>'
=>
'>'
,
'"'
=>
'"'
);
sub
attribute_escape {
my
(
$str
) =
@_
;
return
""
unless
defined
$str
;
$str
=~ s/([&<>"])/
$attribute_escape
{$1}/sg;
$str
}
my
%content_escape
= (
'&'
=>
'&'
,
'<'
=>
'<'
,
'>'
=>
'>'
);
sub
content_escape {
my
(
$str
) =
@_
;
$str
=~ s/([&<>])/
$content_escape
{$1}/sg;
$str
}
sub
pxmlforce;
sub
pxmlforce {
@_
== 1 or fp_croak_arity 1;
my
(
$v
) =
@_
;
if
(
my
$r
=
ref
$v
) {
if
(
$r
eq
"CODE"
) {
pxmlforce(
&$r
())
}
else
{
force
$v
}
}
else
{
$v
}
}
sub
object_force_escape {
@_
== 4 or fp_croak_arity 4;
my
(
$v
,
$string_method_for_context
,
$escape
,
$fh
) =
@_
;
if
(
defined
blessed
$v
) {
if
(
my
$m
=
$v
->can(
$string_method_for_context
)) {
return
&$m
(
$v
,
$fh
);
}
elsif
(
$m
=
$v
->can(
"string"
)
)
{
return
&$escape
(
&$m
(
$v
));
}
}
die
"unexpected type of reference that doesn't have a 'string' method: "
. (show
$v
);
}
sub
_attribute_val_to_string {
my
(
$v
,
$fh
) =
@_
;
my
$ref
=
ref
$v
;
if
(
length
(
$ref
)) {
if
(
$ref
eq
"ARRAY"
) {
join
(
""
,
map
{ _attribute_val_to_string(
$_
,
$fh
) }
@$v
)
}
elsif
(is_pxmlflush(
$v
)) {
flush
$fh
or
die
$!;
""
}
else
{
object_force_escape(pxmlforce(
$v
),
"pxml_serialized_attribute_string"
,
\
&attribute_escape
,
$fh
)
}
}
else
{
attribute_escape
$v
}
}
sub
_pxml_print_fragment_fast {
@_
== 4 or fp_croak_arity 4;
my
(
$v
,
$fh
,
$html5compat
,
$void_element_h
) =
@_
;
weaken
$_
[0]
if
ref
$_
[0];
LP: {
if
(
my
$ref
=
ref
$v
) {
if
(
defined
(
my
$class
= blessed
$v
)) {
if
(
$ref
eq
"PXML::Element"
or
$ref
eq
"PXML::_::XHTML"
or
$v
->isa(
"PXML::Element"
)
)
{
PXML:
my
$n
=
$v
->name;
print
$fh
"<$n"
or
die
$!;
if
(
my
$attrs
=
$v
->maybe_attributes) {
for
my
$k
(
sort
keys
%$attrs
) {
print
$fh
" $k=\""
or
die
$!;
my
$str
= _attribute_val_to_string
$$attrs
{
$k
},
$fh
;
print
$fh
"$str\""
or
die
$!;
}
}
my
$body
=
$v
->body;
my
$looksempty
=
(
not
defined
$body
or (not
ref
$body
and
length
(
$body
) == 0)
or (
is_somearray(
$body
) and (
not
@$body
or (
@$body
== 1
and
(
not
defined
$$body
[0]
or (is_somearray(
$$body
[0])
and not @{
$$body
[0] })
or is_empty_string(
$$body
[0])
)
)
)
)
);
my
$selfreferential
;
if
(
$html5compat
) {
if
(
$$void_element_h
{
$n
}) {
if
(
$looksempty
) {
$selfreferential
= 1;
}
else
{
my
$isempty
=
is_null(stream_mixed_flatten(
$body
));
$selfreferential
=
$isempty
;
warn
"html5 compatible serialization requested "
.
"but got void element '$n' that is not empty"
if
not
$isempty
;
}
}
else
{
$selfreferential
= 0;
}
}
else
{
$selfreferential
=
$looksempty
;
}
if
(
$selfreferential
) {
print
$fh
"/>"
or
die
$!;
}
else
{
print
$fh
">"
or
die
$!;
no
warnings
"recursion"
;
_pxml_print_fragment_fast(
$body
,
$fh
,
$html5compat
,
$void_element_h
);
print
$fh
"</$n>"
or
die
$!;
}
}
elsif
(
my
$car_and_cdr
=
$v
->can(
"car_and_cdr"
)) {
PAIR:
(
$a
,
$v
) =
&$car_and_cdr
(
$v
);
_pxml_print_fragment_fast(
$a
,
$fh
,
$html5compat
,
$void_element_h
);
redo
LP;
}
elsif
(
my
$for_each
=
$v
->can(
"for_each"
)) {
&$for_each
(
$v
,
sub
{
my
(
$a
) =
@_
;
_pxml_print_fragment_fast(
$a
,
$fh
,
$html5compat
,
$void_element_h
);
}
);
}
else
{
my
$v2
= force(
$v
, 1);
my
$addr2
= refaddr(
$v2
);
if
(
defined
(
$addr2
) and
$addr2
!= refaddr(
$v
)) {
$v
=
$v2
;
redo
LP;
}
elsif
(is_somearray(
$v
)) {
no
warnings
"recursion"
;
for
(
@$v
) {
_pxml_print_fragment_fast(
$_
,
$fh
,
$html5compat
,
$void_element_h
);
}
}
elsif
(is_pxmlflush
$v
) {
flush
$fh
or
die
$!
}
else
{
print
$fh
object_force_escape(
$v
,
"pxml_serialized_body_string"
, \
&content_escape
,
$fh
)
or
die
$!;
}
}
}
else
{
if
(is_somearray(
$v
)) {
no
warnings
"recursion"
;
for
(
@$v
) {
_pxml_print_fragment_fast(
$_
,
$fh
,
$html5compat
,
$void_element_h
);
}
}
elsif
(
$ref
eq
"CODE"
) {
$v
=
&$v
();
redo
LP;
}
elsif
(is_null
$v
) {
die
"OBSOLETE?"
;
}
else
{
warn
"XXX when does this happen?"
;
$ref
or
die
"BUG"
;
goto
PXML
if
$v
->isa(
"PXML::Element"
);
goto
PAIR
if
is_pair
$v
;
print
$fh
object_force_escape(
$v
,
"pxml_serialized_body_string"
, \
&content_escape
,
$fh
)
or
die
$!;
}
}
}
elsif
(not
defined
$v
) {
}
else
{
$v
=~ s/([&<>])/
$content_escape
{$1}/sg;
print
$fh
$v
or
die
$!;
}
}
}
sub
pxml_print_fragment_fast {
@_
== 2 or fp_croak_arity 2;
my
(
$v
,
$fh
) =
@_
;
weaken
$_
[0]
if
ref
$_
[0];
my
$no_element
=
sub
{
@_
= (
$v
,
$fh
,
undef
,
undef
);
goto
\
&_pxml_print_fragment_fast
;
};
my
$with_first_element
=
sub
{
my
(
$firstel
) =
@_
;
weaken
$_
[0]
if
ref
$_
[0];
my
$html5compat
=
$firstel
->require_printing_nonvoid_elements_nonselfreferential;
@_
= (
$v
,
$fh
,
$html5compat
,
(
$html5compat
and
$firstel
->void_element_h));
goto
\
&_pxml_print_fragment_fast
;
};
if
(
length
(
my
$r
=
ref
$v
)) {
if
(
defined
blessed
$v
and
$v
->isa(
"PXML::XHTML"
)) {
@_
= (
$v
);
goto
&$with_first_element
;
}
else
{
my
$s
= force(stream_mixed_flatten(
$v
)->filter(\
&is_pxml_element
));
if
(is_null
$s
) {
goto
&$no_element
}
else
{
@_
= (car
$s
);
goto
&$with_first_element
;
}
}
}
else
{
goto
&$no_element
}
}
sub
pxml_xhtml_print_fast {
@_
>= 2 and
@_
<= 3 or fp_croak_arity
"2-3"
;
my
(
$v
,
$fh
,
$maybe_lang
) =
@_
;
weaken
$_
[0]
if
ref
$_
[0];
if
(not
ref
$v
or (
defined
(blessed
$v
) and not
$v
->isa(
"PXML::Element"
))) {
die
"not an element: "
. (show
$v
);
}
if
(not
"html"
eq
$v
->name) {
die
"not an 'html' element: "
. (show
$v
);
}
xprint(
$fh
,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
);
xprint(
$fh
,
);
my
$v2
=
$v
->maybe_attributes ?
$v
:
$v
->attributes_set(
do
{
my
$lang
=
$maybe_lang
or
die
"missing 'lang' attribute from html element and no lang option given"
;
+{
"xml:lang"
=>
$lang
,
lang
=>
$lang
}
}
);
@_
= (
$v2
,
$fh
);
goto
\
&pxml_print_fragment_fast
;
}
sub
pxml_xhtml_print;
*pxml_xhtml_print
= \
&pxml_xhtml_print_fast
;
sub
pxml_print {
@_
== 2 or fp_croak_arity 2;
my
(
$v
,
$fh
) =
@_
;
weaken
$_
[0]
if
ref
$_
[0];
xprintln(
$fh
,
q{<?xml version="1.0"?>}
);
pxml_print_fragment_fast(
$v
,
$fh
);
}
sub
putxmlfile {
@_
== 2 or fp_croak_arity 2;
my
(
$path
,
$xml
) =
@_
;
weaken
$_
[1]
if
ref
$_
[0];
my
$f
= xopen_write
$path
;
binmode
(
$f
,
":utf8"
) or
die
"binmode"
;
pxml_print(
$xml
,
$f
);
$f
->xclose;
}
sub
PXML::Element::xmlfile {
my
(
$v
,
$path
) =
@_
;
weaken
$_
[0];
putxmlfile(
$path
,
$v
)
}
sub
puthtmlfile {
@_
>= 2 and
@_
<= 3 or fp_croak_arity
"2-3"
;
my
(
$path
,
$v
,
$maybe_lang
) =
@_
;
weaken
$_
[1]
if
ref
$_
[0];
my
$out
= xopen_write(
$path
);
binmode
$out
,
":utf8"
or
die
"binmode"
;
pxml_xhtml_print_fast(
$v
,
$out
,
$maybe_lang
||
"en"
);
$out
->xclose;
}
sub
PXML::Element::htmlfile {
my
(
$v
,
$path
,
$maybe_lang
) =
@_
;
weaken
$_
[0];
puthtmlfile(
$path
,
$v
,
$maybe_lang
)
}
1