our
$FONT_ELEMENT_NAME
=
"ltx:text"
;
our
$MATH_TOKEN_NAME
=
"ltx:XMTok"
;
our
$MATH_HINT_NAME
=
"ltx:XMHint"
;
DebuggableFeature(
'document'
);
sub
new {
my
(
$class
,
$model
) =
@_
;
my
$doc
= XML::LibXML::Document->new(
"1.0"
,
"UTF-8"
);
return
bless
{
document
=>
$doc
,
node
=>
$doc
,
model
=>
$model
,
idstore
=> {},
labelstore
=> {},
node_fonts
=> {},
node_boxes
=> {},
node_properties
=> {},
pending
=> [],
progress
=> 0 },
$class
; }
our
$CONSTRUCTION_PROGRESS_QUANTUM
= 500;
sub
getDocument {
my
(
$self
) =
@_
;
return
$$self
{document}; }
sub
getModel {
my
(
$self
) =
@_
;
return
$$self
{model}; }
sub
documentElement {
my
(
$self
) =
@_
;
return
$$self
{document}->documentElement; }
sub
getNode {
my
(
$self
) =
@_
;
return
$$self
{node}; }
sub
setNode {
my
(
$self
,
$node
) =
@_
;
closeText_internal(
$self
);
my
$type
=
$node
->nodeType;
if
(
$type
== XML_DOCUMENT_FRAG_NODE) {
my
@n
=
$node
->childNodes;
if
(
@n
> 1) {
Error(
'unexpected'
,
'multiple-nodes'
,
$self
,
"Cannot set insertion point to a DOCUMENT_FRAG_NODE"
, Stringify(
$node
)); }
elsif
(
@n
< 1) {
Error(
'unexpected'
,
'empty-nodes'
,
$self
,
"Cannot set insertion point to an empty DOCUMENT_FRAG_NODE"
); }
$node
=
$n
[0]; }
$$self
{node} =
$node
;
return
; }
sub
getLocator {
my
(
$self
) =
@_
;
if
(
my
$box
= getNodeBox(
$self
,
$$self
{node})) {
return
$box
->getLocator; }
else
{
return
; } }
sub
getElement {
my
(
$self
) =
@_
;
my
$node
=
$$self
{node};
$node
=
$node
->parentNode
if
$node
->getType == XML_TEXT_NODE;
return
(
$node
->getType == XML_DOCUMENT_NODE ?
undef
:
$node
); }
sub
getChildElements {
my
(
$self
,
$node
) =
@_
;
return
(!
$node
? () :
grep
{
$_
->nodeType == XML_ELEMENT_NODE }
$node
->childNodes); }
sub
getLastChildElement {
my
(
$self
,
$node
) =
@_
;
if
(
$node
->hasChildNodes) {
my
$n
=
$node
->lastChild;
while
(
$n
&&
$n
->nodeType != XML_ELEMENT_NODE) {
$n
=
$node
->previousSibling; }
return
$n
; } }
sub
getFirstChildElement {
my
(
$self
,
$node
) =
@_
;
if
(
$node
->hasChildNodes) {
my
$n
=
$node
->firstChild;
while
(
$n
&&
$n
->nodeType != XML_ELEMENT_NODE) {
$n
=
$n
->nextSibling; }
return
$n
; }
return
; }
sub
getSecondChildElement {
my
(
$self
,
$node
) =
@_
;
my
$first_child
= getFirstChildElement(
$self
,
$node
);
my
$second_child
=
$first_child
&&
$first_child
->nextSibling;
while
(
$second_child
&&
$second_child
->nodeType != XML_ELEMENT_NODE) {
$second_child
=
$second_child
->nextSibling; }
return
$second_child
; }
sub
findnodes {
my
(
$self
,
$xpath
,
$node
) =
@_
;
return
$$self
{model}->getXPath->findnodes(
$xpath
, (
$node
||
$$self
{document})); }
sub
findnode {
my
(
$self
,
$xpath
,
$node
) =
@_
;
my
@nodes
=
$$self
{model}->getXPath->findnodes(
$xpath
, (
$node
||
$$self
{document}));
return
(
@nodes
?
$nodes
[0] :
undef
); }
sub
getNodeQName {
my
(
$self
,
$node
) =
@_
;
return
$node
&&
$$self
{model}->getNodeQName(
$node
); }
sub
canContain {
my
(
$self
,
$tag
,
$child
) =
@_
;
my
$model
=
$$self
{model};
$tag
=
$model
->getNodeQName(
$tag
)
if
ref
$tag
;
$child
=
$model
->getNodeQName(
$child
)
if
ref
$child
;
return
$model
->canContain(
$tag
,
$child
); }
sub
canContainIndirect {
my
(
$self
,
$tag
,
$child
) =
@_
;
my
$model
=
$$self
{model};
$tag
=
$model
->getNodeQName(
$tag
)
if
ref
$tag
;
$child
=
$model
->getNodeQName(
$child
)
if
ref
$child
;
my
$imodel
=
$STATE
->lookupValue(
'INDIRECT_MODEL'
);
if
(!
$imodel
) {
$imodel
= computeIndirectModel(
$self
);
$STATE
->assignValue(
INDIRECT_MODEL
=>
$imodel
,
'global'
); }
return
$$imodel
{
$tag
}{
$child
}; }
sub
computeIndirectModel {
my
(
$self
) =
@_
;
my
$model
=
$$self
{model};
my
$imodel
= {};
local
%::OPENABILITY = ();
foreach
my
$tag
(
$model
->getTags) {
my
$x
;
if
((
$x
=
$STATE
->lookupMapping(
'TAG_PROPERTIES'
,
$tag
)) && (
$x
=
$$x
{autoOpen})) {
$::OPENABILITY{
$tag
} = (
$x
=~ /^\d*\.\d*$/ ?
$x
: 1); }
else
{
$::OPENABILITY{
$tag
} = 0; } }
foreach
my
$tag
(
$model
->getTags) {
local
%::DESC = ();
computeIndirectModel_aux(
$model
,
$tag
,
''
, 1);
foreach
my
$kid
(
sort
keys
%::DESC) {
my
$best
= 0;
foreach
my
$start
(
sort
keys
%{ $::DESC{
$kid
} }) {
if
((
$tag
ne
$kid
) && (
$tag
ne
$start
) && ($::DESC{
$kid
}{
$start
} >
$best
)) {
$$imodel
{
$tag
}{
$kid
} =
$start
;
$best
= $::DESC{
$kid
}{
$start
}; } } } }
if
(
$$model
{permissive}) {
$$imodel
{
'#Document'
}{
'#PCDATA'
} =
'ltx:p'
; }
return
$imodel
; }
sub
computeIndirectModel_aux {
my
(
$model
,
$tag
,
$start
,
$desirability
) =
@_
;
my
$x
;
foreach
my
$kid
(
$model
->getTagContents(
$tag
)) {
next
if
$::DESC{
$kid
}{
$start
};
$::DESC{
$kid
}{
$start
} =
$desirability
if
$start
;
if
((
$kid
ne
'#PCDATA'
) && (
$x
= $::OPENABILITY{
$kid
})) {
computeIndirectModel_aux(
$model
,
$kid
,
$start
||
$kid
,
$desirability
*
$x
); } }
return
; }
sub
canContainSomehow {
my
(
$self
,
$tag
,
$child
) =
@_
;
my
$model
=
$$self
{model};
$tag
=
$model
->getNodeQName(
$tag
)
if
ref
$tag
;
$child
=
$model
->getNodeQName(
$child
)
if
ref
$child
;
return
$model
->canContain(
$tag
,
$child
) || canContainIndirect(
$self
,
$tag
,
$child
); }
sub
canHaveAttribute {
my
(
$self
,
$tag
,
$attrib
) =
@_
;
my
$model
=
$$self
{model};
$tag
=
$model
->getNodeQName(
$tag
)
if
ref
$tag
;
return
$model
->canHaveAttribute(
$tag
,
$attrib
); }
sub
canAutoOpen {
my
(
$self
,
$tag
) =
@_
;
if
(
my
$props
=
$STATE
->lookupMapping(
'TAG_PROPERTIES'
,
$tag
)) {
return
$$props
{autoOpen}; } }
sub
canAutoClose {
my
(
$self
,
$node
) =
@_
;
my
$t
=
$node
->nodeType;
my
$model
=
$$self
{model};
my
$props
;
return
(
$t
== XML_TEXT_NODE) || (
$t
== XML_COMMENT_NODE)
|| ((
$t
== XML_ELEMENT_NODE)
&& !
$node
->getAttribute(
'_noautoclose'
)
&& (
$node
->getAttribute(
'_autoclose'
)
|| ((
$props
=
$STATE
->lookupMapping(
'TAG_PROPERTIES'
, getNodeQName(
$self
,
$node
)))
&&
$$props
{autoClose})));
}
sub
getTagActionList {
my
(
$self
,
$tag
,
$when
) =
@_
;
$tag
=
$$self
{model}->getNodeQName(
$tag
)
if
ref
$tag
;
my
(
$p
,
$n
) = (
undef
,
$tag
);
if
(
$tag
=~ /^([^:]+):(.+)$/) {
(
$p
,
$n
) = ($1, $2); }
my
$when0
=
$when
.
':early'
;
my
$when1
=
$when
.
':late'
;
my
$taghash
=
$STATE
->lookupMapping(
'TAG_PROPERTIES'
,
$tag
) || {};
my
$nshash
= ((
defined
$p
) &&
$STATE
->lookupMapping(
'TAG_PROPERTIES'
,
$p
.
':*'
)) || {};
my
$allhash
=
$STATE
->lookupMapping(
'TAG_PROPERTIES'
,
'*'
) || {};
my
$v
;
return
(
((
$v
=
$$taghash
{
$when0
}) ?
@$v
: ()),
((
$v
=
$$nshash
{
$when0
}) ?
@$v
: ()),
((
$v
=
$$allhash
{
$when0
}) ?
@$v
: ()),
((
$v
=
$$taghash
{
$when
}) ?
@$v
: ()),
((
$v
=
$$nshash
{
$when
}) ?
@$v
: ()),
((
$v
=
$$allhash
{
$when
}) ?
@$v
: ()),
((
$v
=
$$taghash
{
$when1
}) ?
@$v
: ()),
((
$v
=
$$nshash
{
$when1
}) ?
@$v
: ()),
((
$v
=
$$allhash
{
$when1
}) ?
@$v
: ()),
); }
sub
doctest {
my
(
$self
,
$when
,
$severe
) =
@_
;
local
$LaTeXML::NNODES
= 0;
Debug(
"START DOC TEST $when....."
);
if
(
my
$root
= getDocument(
$self
)->documentElement) {
doctest_rec(
$self
,
undef
,
$root
,
$severe
); }
Debug(
"...("
.
$LaTeXML::NNODES
.
" nodes)....DONE"
);
return
; }
sub
doctest_rec {
my
(
$self
,
$parent
,
$node
,
$severe
) =
@_
;
doctest_head(
$self
,
$parent
,
$node
,
$severe
);
my
$type
=
$node
->nodeType;
if
(
$type
== XML_ELEMENT_NODE) {
Debug(
"ELEMENT "
.
join
(
' '
,
"<"
.
$$self
{model}->getNodeQName(
$node
),
(
map
{
$_
->nodeName .
'="'
.
$_
->getValue .
'"'
}
$node
->attributes)) .
">"
)
if
$severe
;
doctest_children(
$self
,
$node
,
$severe
); }
elsif
(
$type
== XML_ATTRIBUTE_NODE) {
Debug(
"ATTRIBUTE "
.
$node
->nodeName .
"=>"
.
$node
->getValue)
if
$severe
; }
elsif
(
$type
== XML_TEXT_NODE) {
Debug(
"TEXT "
.
$node
->textContent)
if
$severe
; }
elsif
(
$type
== XML_CDATA_SECTION_NODE) {
Debug(
"CDATA "
.
$node
->textContent)
if
$severe
; }
elsif
(
$type
== XML_PI_NODE) {
Debug(
"PI "
.
$node
->localname .
" "
.
$node
->getData)
if
$severe
; }
elsif
(
$type
== XML_COMMENT_NODE) {
Debug(
"COMMENT "
.
$node
->textContent)
if
$severe
; }
elsif
(
$type
== XML_DOCUMENT_FRAG_NODE) {
Debug(
"DOCUMENT_FRAG"
)
if
$severe
;
doctest_children(
$self
,
$node
,
$severe
); }
else
{
Debug(
"OTHER $type"
)
if
$severe
; }
return
; }
sub
doctest_head {
my
(
$self
,
$parent
,
$node
,
$severe
) =
@_
;
Debug(
" NODE $$node ["
)
if
$severe
;
if
(!
$node
->ownerDocument->isSameNode(getDocument(
$self
))) {
Debug(
"d!"
)
if
$severe
; }
if
(
$parent
&& !
$node
->parentNode->isSameNode(
$parent
)) {
Debug(
"p!"
)
if
$severe
; }
my
$type
=
$node
->nodeType;
Debug(
"t] "
)
if
$severe
;
return
; }
sub
doctest_children {
my
(
$self
,
$node
,
$severe
) =
@_
;
Debug(
"[fc"
)
if
$severe
;
my
$c
=
$node
->firstChild;
while
(
$c
) {
Debug(
"]"
)
if
$severe
;
doctest_rec(
$self
,
$node
,
$c
,
$severe
);
Debug(
"[nc"
)
if
$severe
;
$c
=
$c
->nextSibling; }
Debug(
"]done"
)
if
$severe
;
return
; }
sub
finalize {
my
(
$self
) =
@_
;
pruneXMDuals(
$self
);
if
(
my
$root
= getDocument(
$self
)->documentElement) {
local
$LaTeXML::FONT
= LaTeXML::Common::Font->textDefault;
finalize_rec(
$self
,
$root
);
set_RDFa_prefixes(getDocument(
$self
),
$STATE
->lookupValue(
'RDFa_prefixes'
)); }
return
$self
; }
sub
finalize_rec {
my
(
$self
,
$node
) =
@_
;
my
$model
=
$$self
{model};
no
warnings
'recursion'
;
my
$qname
=
$model
->getNodeQName(
$node
);
my
$declared_font
= (
$node
->getAttribute(
'_standalone_font'
)
? LaTeXML::Common::Font->textDefault :
$LaTeXML::FONT
);
my
$desired_font
=
$LaTeXML::FONT
;
my
%pending_declaration
= ();
if
(
my
$comment
=
$node
->getAttribute(
'_pre_comment'
)) {
$node
->parentNode->insertBefore(XML::LibXML::Comment->new(
$comment
),
$node
); }
if
(
my
$comment
=
$node
->getAttribute(
'_comment'
)) {
$node
->parentNode->insertAfter(XML::LibXML::Comment->new(
$comment
),
$node
); }
if
(
my
$font_attr
=
$node
->getAttribute(
'_font'
)) {
$desired_font
=
$$self
{node_fonts}{
$font_attr
};
%pending_declaration
=
$desired_font
->relativeTo(
$declared_font
);
if
((
$node
->hasChildNodes ||
$node
->getAttribute(
'_force_font'
))
&&
scalar
(
keys
%pending_declaration
)) {
foreach
my
$attr
(
keys
%pending_declaration
) {
if
(
$model
->canHaveAttribute(
$qname
,
$attr
)) {
my
$value
=
$pending_declaration
{
$attr
}{value};
if
(
$attr
eq
'class'
) {
if
(
my
$ovalue
=
$node
->getAttribute(
'class'
)) {
$value
.=
' '
.
$ovalue
; } }
setAttribute(
$self
,
$node
,
$attr
=>
$value
);
$declared_font
=
$declared_font
->merge(%{
$pending_declaration
{
$attr
}{properties} });
delete
$pending_declaration
{
$attr
}; } }
} }
if
(
$STATE
&&
$STATE
->lookupValue(
'GENERATE_IDS'
)
&& !
$node
->hasAttribute(
'xml:id'
)
&& canHaveAttribute(
$self
,
$qname
,
'xml:id'
)
&& (
$qname
ne
'ltx:document'
)) {
LaTeXML::Package::GenerateID(
$self
,
$node
); }
local
$LaTeXML::FONT
=
$declared_font
;
foreach
my
$child
(
$node
->childNodes) {
my
$type
=
$child
->nodeType;
if
(
$type
== XML_ELEMENT_NODE) {
my
$was_forcefont
=
$child
->getAttribute(
'_force_font'
);
finalize_rec(
$self
,
$child
);
if
((
$model
->getNodeQName(
$child
) eq
$FONT_ELEMENT_NAME
)
&& !
$was_forcefont
&& !
$child
->hasAttributes) {
my
@grandchildren
=
$child
->childNodes;
if
(!
grep
{ !canContain(
$self
,
$qname
,
$_
) }
@grandchildren
) {
replaceNode(
$self
,
$child
,
@grandchildren
); } }
}
elsif
(
$type
== XML_TEXT_NODE) {
my
$elementname
=
$pending_declaration
{element}{value} ||
$FONT_ELEMENT_NAME
;
delete
$pending_declaration
{element};
foreach
my
$key
(
keys
%pending_declaration
) {
delete
$pending_declaration
{
$key
}
unless
canHaveAttribute(
$self
,
$elementname
,
$key
); }
if
(canContain(
$self
,
$qname
,
$elementname
)
&&
scalar
(
keys
%pending_declaration
)) {
my
$text
= wrapNodes(
$self
,
$elementname
,
$child
);
foreach
my
$attr
(
keys
%pending_declaration
) {
my
$value
=
$pending_declaration
{
$attr
}{value};
if
(
$attr
eq
'class'
) {
if
(
my
$ovalue
=
$text
->getAttribute(
'class'
)) {
$value
.=
' '
.
$ovalue
; } }
setAttribute(
$self
,
$text
,
$attr
=>
$value
); }
finalize_rec(
$self
,
$text
);
}
} }
foreach
my
$attr
(
$node
->attributes) {
my
$n
=
$attr
->nodeName;
$node
->removeAttribute(
$n
)
if
$n
&&
$n
=~ /^_/; }
return
; }
sub
toString {
my
(
$self
,
$format
) =
@_
;
return
serialize_aux(
$self
, getDocument(
$self
), 0, 0, 0); }
sub
serialize_aux {
my
(
$self
,
$node
,
$depth
,
$noindent
,
$heuristic
) =
@_
;
no
warnings
'recursion'
;
my
$type
=
$node
->nodeType;
my
$model
=
$$self
{model};
my
$indent
= (
' '
x
$depth
);
if
(
$type
== XML_DOCUMENT_NODE) {
my
@children
=
$node
->childNodes;
return
join
(
''
,
'<?xml version="1.0" encoding="UTF-8"?>'
,
"\n"
,
(
map
{ serialize_aux(
$self
,
$_
,
$depth
,
$noindent
,
$heuristic
) }
@children
)); }
elsif
(
$type
== XML_ELEMENT_NODE) {
my
$tag
=
$model
->getNodeDocumentQName(
$node
);
my
@children
=
$node
->childNodes;
my
@anodes
=
$node
->attributes;
my
%nsnodes
=
map
{
$model
->getNodeDocumentQName(
$_
) => serialize_attr(
$_
->nodeValue) }
grep
{
$_
->nodeType == XML_NAMESPACE_DECL }
@anodes
;
my
%atnodes
=
map
{
$model
->getNodeDocumentQName(
$_
) => serialize_attr(
$_
->nodeValue) }
grep
{
$_
->nodeType == XML_ATTRIBUTE_NODE }
@anodes
;
my
$start
=
join
(
' '
,
'<'
.
$tag
,
(
map
{
$_
.
'="'
.
$nsnodes
{
$_
} .
'"'
}
sort
keys
%nsnodes
),
(
map
{
$_
.
'="'
.
$atnodes
{
$_
} .
'"'
}
sort
keys
%atnodes
)
);
my
$noindent_children
= (
$heuristic
?
$noindent
||
grep
{
$_
->nodeType == XML_TEXT_NODE }
@children
:
$model
->canContain(getNodeQName(
$self
,
$node
),
'#PCDATA'
));
return
join
(
''
,
(
$noindent
?
''
:
$indent
),
$start
,
(
scalar
(
@children
)
? (
'>'
, (
$noindent_children
?
''
:
"\n"
),
(
map
{ serialize_aux(
$self
,
$_
,
$depth
+ 1,
$noindent_children
,
$heuristic
) }
@children
),
(
$noindent_children
?
''
:
$indent
),
'</'
.
$tag
.
'>'
, (
$noindent
?
''
:
"\n"
))
: (
'/>'
. (
$noindent
?
''
:
"\n"
)))); }
elsif
(
$type
== XML_TEXT_NODE) {
return
serialize_string(
$node
->textContent); }
elsif
(
$type
== XML_PI_NODE) {
return
join
(
''
, (
$noindent
?
''
:
$indent
),
$node
->toString, (
$noindent
?
''
:
"\n"
)); }
elsif
(
$type
== XML_COMMENT_NODE) {
return
join
(
''
,
'<!-- '
, serialize_string(
$node
->textContent),
'-->'
); }
else
{
return
''
; } }
sub
serialize_string {
my
(
$string
) =
@_
;
$string
=~ s/&/
&
;/g;
$string
=~ s/>/
>
;/g;
$string
=~ s/</
<
;/g;
$string
=~ s/(?:\x{00}-\x{08}|\x{0B}|\x{0C}|\x{0D}-\x{19})//g;
return
$string
; }
sub
serialize_attr {
my
(
$string
) =
@_
;
$string
= serialize_string(
$string
);
$string
=~ s/"/
"
;/g;
$string
=~ s/\n/&
$string
=~ s/\t/&
return
$string
; }
sub
absorb {
my
(
$self
,
$object
,
%props
) =
@_
;
no
warnings
'recursion'
;
my
@boxes
= (
$object
);
my
@results
= ();
while
(
@boxes
) {
my
$box
=
shift
(
@boxes
);
next
unless
defined
$box
;
if
(((
ref
$box
) ||
'nothing'
) eq
'LaTeXML::Core::List'
) {
unshift
(
@boxes
,
$box
->unlist);
next
; }
if
(
ref
$box
) {
local
$LaTeXML::BOX
=
$box
;
if
(
$LaTeXML::RECORDING_CONSTRUCTION
||
defined
wantarray
) {
my
@n
= ();
{
local
$LaTeXML::RECORDING_CONSTRUCTION
= 1;
local
@LaTeXML::CONSTRUCTED_NODES
= ();
$box
->beAbsorbed(
$self
);
@n
=
@LaTeXML::CONSTRUCTED_NODES
; }
map
{ recordConstructedNode(
$self
,
$_
) }
@n
;
push
(
@results
,
@n
); }
else
{
push
(
@results
,
$box
->beAbsorbed(
$self
)); } }
elsif
(!
$props
{isMath}) {
push
(
@results
, openText(
$self
,
$box
,
$props
{font} || (
$LaTeXML::BOX
&&
$LaTeXML::BOX
->getFont))); }
elsif
(
$$self
{model}->getNodeQName(
$$self
{node}) eq
$MATH_TOKEN_NAME
) {
push
(
@results
, openMathText_internal(
$self
,
$box
)); }
else
{
push
(
@results
, insertMathToken(
$self
,
$box
,
font
=>
$props
{font})); } }
return
@results
; }
sub
recordConstructedNode {
my
(
$self
,
$node
) =
@_
;
if
((
defined
$LaTeXML::RECORDING_CONSTRUCTION
)
&& (!
@LaTeXML::CONSTRUCTED_NODES
|| !
$node
->isSameNode(
$LaTeXML::CONSTRUCTED_NODES
[-1]))) {
push
(
@LaTeXML::CONSTRUCTED_NODES
,
$node
); }
return
; }
sub
filterDeletions {
my
(
$self
,
@nodes
) =
@_
;
my
$doc
=
$$self
{document};
return
grep
{ isDescendantOrSelf(
$_
,
$doc
) }
@nodes
; }
sub
filterChildren {
my
(
$self
,
@node
) =
@_
;
return
()
unless
@node
;
my
@n
= (
shift
(
@node
));
foreach
my
$n
(
@node
) {
push
(
@n
,
$n
)
unless
grep
{ isDescendantOrSelf(
$n
,
$_
); }
@n
; }
return
@n
; }
sub
insertElement {
my
(
$self
,
$qname
,
$content
,
%attrib
) =
@_
;
my
$node
= openElement(
$self
,
$qname
,
%attrib
);
if
(
ref
$content
eq
'ARRAY'
) {
map
{ absorb(
$self
,
$_
) }
@$content
; }
elsif
(
defined
$content
) {
absorb(
$self
,
$content
); }
my
$c
=
$$self
{node};
while
(
$c
&& (
$c
->nodeType != XML_DOCUMENT_NODE) && !
$c
->isSameNode(
$node
)) {
$c
=
$c
->parentNode; }
if
(
$c
->isSameNode(
$node
)) {
closeElement(
$self
,
$qname
); }
return
$node
; }
sub
insertMathToken {
my
(
$self
,
$string
,
%attributes
) =
@_
;
$attributes
{role} =
'UNKNOWN'
unless
$attributes
{role};
my
$qname
= (
$attributes
{isSpace} ?
$MATH_HINT_NAME
:
$MATH_TOKEN_NAME
);
my
$cur_qname
=
$$self
{model}->getNodeQName(
$$self
{node});
if
(
$attributes
{isSpace} && (
defined
$string
) && (
$string
=~ /^\s*$/)) {
$string
=
undef
; }
if
((
$qname
eq
$MATH_TOKEN_NAME
) && (
$cur_qname
eq
$qname
)) {
openMathText_internal(
$self
,
$string
)
if
defined
$string
;
return
$$self
{node}; }
else
{
my
$node
= openElement(
$self
,
$qname
,
%attributes
);
my
$box
=
$attributes
{_box} ||
$LaTeXML::BOX
;
my
$font
=
$attributes
{font} ||
$box
->getFont;
setNodeFont(
$self
,
$node
,
$font
);
setNodeBox(
$self
,
$node
,
$box
);
openMathText_internal(
$self
,
$string
)
if
defined
$string
;
closeNode_internal(
$self
,
$node
);
return
$node
; } }
sub
insertComment {
my
(
$self
,
$text
) =
@_
;
chomp
(
$text
);
$text
=~ s/\-\-+/__/g;
my
$comment
;
my
$node
= getElement(
$self
);
my
$prev
=
$node
&&
$node
->lastChild;
my
$prevtype
=
$prev
&&
$prev
->nodeType;
if
(
$$self
{node}->nodeType == XML_DOCUMENT_NODE) {
push
(@{
$$self
{pending} },
$comment
=
$$self
{document}->createComment(
' '
.
$text
.
' '
)); }
elsif
(
$prevtype
&& (
$prevtype
== XML_COMMENT_NODE)) {
$comment
=
$prev
;
$comment
->setData(
$comment
->data .
"\n "
.
$text
.
' '
); }
elsif
(
$prevtype
&& (
$prevtype
== XML_TEXT_NODE)) {
if
((
$comment
=
$prev
->previousSibling) && (
$comment
->nodeType == XML_COMMENT_NODE)) {
$comment
=
$node
->appendChild(
$$self
{document}->createComment(
' '
.
$text
.
' '
)); }
else
{
$comment
=
$node
->insertBefore(
$$self
{document}->createComment(
' '
.
$text
.
' '
),
$prev
); } }
else
{
$comment
=
$node
->appendChild(
$$self
{document}->createComment(
' '
.
$text
.
' '
)); }
return
$comment
; }
sub
insertPI {
my
(
$self
,
$op
,
%attrib
) =
@_
;
my
@keys
= ((
map
{ (
$attrib
{
$_
} ? (
$_
) : ()) }
qw(class package options)
),
(
grep
{
$_
!~ /^(?:class|
package
|options)$/ }
sort
keys
%attrib
));
my
$data
=
join
(
' '
,
map
{
$_
.
"=\""
. ToString(
$attrib
{
$_
}) .
"\""
}
@keys
);
my
$pi
=
$$self
{document}->createProcessingInstruction(
$op
,
$data
);
closeText_internal(
$self
);
if
(
$$self
{node}->nodeType == XML_DOCUMENT_NODE) {
push
(@{
$$self
{pending} },
$pi
); }
else
{
$$self
{document}->insertBefore(
$pi
,
$$self
{document}->documentElement); }
return
$pi
; }
sub
insertElementBefore {
my
(
$self
,
$point
,
$name
,
%attrib
) =
@_
;
my
$new
=
$$self
{document}->createElement(
$name
);
$new
->setNamespace(
$LaTeXML::Common::Model::LTX_NAMESPACE
,
''
, 1);
for
my
$key
(
keys
%attrib
) {
$new
->setAttribute(
$key
,
$attrib
{
$key
}); }
return
$point
->parentNode->insertBefore(
$new
,
$point
); }
sub
openText {
my
(
$self
,
$text
,
$font
) =
@_
;
my
$node
=
$$self
{node};
my
$t
=
$node
->nodeType;
return
if
((!
defined
$text
) ||
$text
=~ /^\s*$/) &&
((
$t
== XML_DOCUMENT_NODE)
|| ((
$t
== XML_ELEMENT_NODE) && !canContain(
$self
,
$node
,
'#PCDATA'
)));
return
if
$font
->getFamily eq
'nullfont'
;
Debug(
"openText \"$text\" /"
. Stringify(
$font
) .
" at "
. Stringify(
$node
))
if
$LaTeXML::DEBUG
{document};
my
$declared_font
= getNodeFont(
$self
,
$node
);
my
%pending_declaration
=
$font
->relativeTo(
$declared_font
);
my
$elementname
=
$pending_declaration
{element}{value} ||
$FONT_ELEMENT_NAME
;
if
((
$t
!= XML_DOCUMENT_NODE)
&& !((
$t
== XML_TEXT_NODE) &&
(
$font
->distance(getNodeFont(
$self
,
$node
->parentNode)) == 0))) {
$node
= closeText_internal(
$self
);
my
(
$bestdiff
,
$closeto
) = (99999,
$node
);
my
$n
=
$node
;
while
(
$n
->nodeType != XML_DOCUMENT_NODE) {
my
$d
=
$font
->distance(getNodeFont(
$self
,
$n
));
if
(
$d
<
$bestdiff
) {
$bestdiff
=
$d
;
$closeto
=
$n
;
last
if
(
$d
== 0); }
last
if
(
$$self
{model}->getNodeQName(
$n
) ne
$elementname
) ||
$n
->getAttribute(
'_noautoclose'
);
$n
=
$n
->parentNode; }
closeToNode(
$self
,
$closeto
)
if
$closeto
ne
$node
;
openElement(
$self
,
$elementname
,
font
=>
$font
,
_fontswitch
=> 1,
_autoopened
=> 1)
if
$bestdiff
> 0;
}
my
$tnode
= openText_internal(
$self
,
$text
);
recordConstructedNode(
$self
,
$tnode
);
return
$tnode
; }
sub
openElement {
my
(
$self
,
$qname
,
%attributes
) =
@_
;
ProgressStep()
if
(
$$self
{progress}++ %
$CONSTRUCTION_PROGRESS_QUANTUM
) == 0;
Debug(
"openElement $qname at "
. Stringify(
$$self
{node}))
if
$LaTeXML::DEBUG
{document};
my
$point
= find_insertion_point(
$self
,
$qname
);
$attributes
{_box} =
$LaTeXML::BOX
unless
$attributes
{_box};
my
$newnode
= openElementAt(
$self
,
$point
,
$qname
,
_font
=>
$attributes
{font} ||
$attributes
{_box}->getFont,
%attributes
);
setNode(
$self
,
$newnode
);
return
$newnode
; }
sub
closeElement {
my
(
$self
,
$qname
) =
@_
;
Debug(
"closeElement $qname at "
. Stringify(
$$self
{node}))
if
$LaTeXML::DEBUG
{document};
closeText_internal(
$self
);
my
(
$node
,
@cant_close
) = (
$$self
{node});
while
(
$node
->nodeType != XML_DOCUMENT_NODE) {
my
$t
=
$$self
{model}->getNodeQName(
$node
);
last
if
(
$t
eq
$qname
) && !((
$t
eq
$FONT_ELEMENT_NAME
) &&
$node
->getAttribute(
'_fontswitch'
));
push
(
@cant_close
,
$node
)
unless
canAutoClose(
$self
,
$node
);
$node
=
$node
->parentNode; }
if
(
$node
->nodeType == XML_DOCUMENT_NODE) {
Error(
'malformed'
,
$qname
,
$self
,
"Attempt to close "
. (
$qname
eq
'#PCDATA'
?
$qname
:
'</'
.
$qname
.
'>'
) .
", which isn't open"
,
"Currently in "
. getInsertionContext(
$self
));
return
; }
else
{
Error(
'malformed'
,
$qname
,
$self
,
"Closing "
. (
$qname
eq
'#PCDATA'
?
$qname
:
'</'
.
$qname
.
'>'
)
.
" whose open descendents do not auto-close"
,
"Descendents are "
.
join
(
', '
,
map
{ Stringify(
$_
) }
@cant_close
))
if
@cant_close
;
closeNode_internal(
$self
,
$node
);
return
$node
; } }
sub
isOpenable {
my
(
$self
,
$qname
) =
@_
;
my
$node
=
$$self
{node};
while
(
$node
) {
return
1
if
canContainSomehow(
$self
,
$node
,
$qname
);
return
0
unless
canAutoClose(
$self
,
$node
);
$node
=
$node
->parentNode; }
return
0; }
sub
isCloseable {
my
(
$self
,
@tags
) =
@_
;
my
$node
=
$$self
{node};
$node
=
$node
->parentNode
if
$node
->nodeType == XML_TEXT_NODE;
while
(
my
$qname
=
shift
(
@tags
)) {
while
(1) {
return
if
$node
->nodeType == XML_DOCUMENT_NODE;
my
$this_qname
=
$$self
{model}->getNodeQName(
$node
);
last
if
$this_qname
eq
$qname
;
return
unless
canAutoClose(
$self
,
$node
);
$node
=
$node
->parentNode; }
$node
=
$node
->parentNode
if
@tags
; }
return
$node
; }
sub
maybeCloseElement {
my
(
$self
,
$qname
) =
@_
;
Debug(
"maybeCloseNode(int) $qname"
)
if
$LaTeXML::DEBUG
{document};
if
(
my
$node
= isCloseable(
$self
,
$qname
)) {
closeNode_internal(
$self
,
$node
);
return
$node
; } }
sub
closeToNode {
my
(
$self
,
$node
,
$ifopen
) =
@_
;
my
$model
=
$$self
{model};
my
(
$t
,
@cant_close
) = ();
my
$n
=
$$self
{node};
my
$lastopen
;
while
(((
$t
=
$n
->getType) != XML_DOCUMENT_NODE) && !
$n
->isSameNode(
$node
)) {
push
(
@cant_close
,
$n
)
unless
canAutoClose(
$self
,
$n
);
$lastopen
=
$n
;
$n
=
$n
->parentNode; }
if
(
$t
== XML_DOCUMENT_NODE) {
Error(
'malformed'
,
$model
->getNodeQName(
$node
),
$self
,
"Attempt to close to "
. Stringify(
$node
) .
", which isn't open"
,
"Currently in "
. getInsertionContext(
$self
))
unless
$ifopen
;
return
; }
else
{
Error(
'malformed'
,
$model
->getNodeQName(
$node
),
$self
,
"Closing to "
. Stringify(
$node
) .
" whose open descendents do not auto-close"
,
"Descendents are "
.
join
(
', '
,
map
{ Stringify(
$_
) }
@cant_close
))
if
@cant_close
;
closeNode_internal(
$self
,
$lastopen
)
if
$lastopen
; }
return
; }
sub
closeNode {
my
(
$self
,
$node
) =
@_
;
my
$model
=
$$self
{model};
my
(
$t
,
@cant_close
) = ();
my
$n
=
$$self
{node};
Debug(
"To closeNode "
. Stringify(
$node
))
if
$LaTeXML::DEBUG
{document};
while
(((
$t
=
$n
->getType) != XML_DOCUMENT_NODE) && !
$n
->isSameNode(
$node
)) {
push
(
@cant_close
,
$n
)
unless
canAutoClose(
$self
,
$n
);
$n
=
$n
->parentNode; }
if
(
$t
== XML_DOCUMENT_NODE) {
Error(
'malformed'
,
$model
->getNodeQName(
$node
),
$self
,
"Attempt to close "
. Stringify(
$node
) .
", which isn't open"
,
"Currently in "
. getInsertionContext(
$self
)); }
else
{
Error(
'malformed'
,
$model
->getNodeQName(
$node
),
$self
,
"Closing "
. Stringify(
$node
) .
" whose open descendents do not auto-close"
,
"Descendents are "
.
join
(
', '
,
map
{ Stringify(
$_
) }
@cant_close
))
if
@cant_close
;
closeNode_internal(
$self
,
$node
); }
return
; }
sub
maybeCloseNode {
my
(
$self
,
$node
) =
@_
;
my
$model
=
$$self
{model};
my
(
$t
,
@cant_close
) = ();
my
$n
=
$$self
{node};
Debug(
"To closeNode "
. Stringify(
$node
))
if
$LaTeXML::DEBUG
{document};
while
(((
$t
=
$n
->getType) != XML_DOCUMENT_NODE) && !
$n
->isSameNode(
$node
)) {
push
(
@cant_close
,
$n
)
unless
canAutoClose(
$self
,
$n
);
$n
=
$n
->parentNode; }
if
(
$t
== XML_DOCUMENT_NODE) { }
else
{
Info(
'malformed'
,
$model
->getNodeQName(
$node
),
$self
,
"Closing "
. Stringify(
$node
) .
" whose open descendents do not auto-close"
,
"Descendents are "
.
join
(
', '
,
map
{ Stringify(
$_
) }
@cant_close
))
if
@cant_close
;
closeNode_internal(
$self
,
$node
); }
return
; }
sub
addAttribute {
my
(
$self
,
$key
,
$value
) =
@_
;
return
unless
defined
$value
;
my
$node
=
$$self
{node};
$node
=
$node
->parentNode
if
$node
->nodeType == XML_TEXT_NODE;
while
((
$node
->nodeType != XML_DOCUMENT_NODE) && !
$$self
{model}->canHaveAttribute(
$node
,
$key
)) {
$node
=
$node
->parentNode; }
if
(
$node
->nodeType == XML_DOCUMENT_NODE) {
Error(
'malformed'
,
$key
,
$self
,
"Attribute $key not allowed in this node or ancestors"
); }
else
{
setAttribute(
$self
,
$node
,
$key
,
$value
); }
return
; }
sub
getInsertionContext {
my
(
$self
,
$levels
) =
@_
;
if
(!
defined
$levels
) {
$levels
= 5
if
(
$LaTeXML::Common::Error::VERBOSITY
<= 1); }
my
$node
=
$$self
{node};
my
$type
=
$node
->nodeType;
if
((
$type
!= XML_TEXT_NODE) && (
$type
!= XML_ELEMENT_NODE) && (
$type
!= XML_DOCUMENT_NODE)) {
Error(
'internal'
,
'context'
,
$self
,
"Insertion point is not an element, document or text: "
, Stringify(
$node
));
return
; }
my
$path
= Stringify(
$node
);
while
(
$node
=
$node
->parentNode) {
if
((
defined
$levels
) && (--
$levels
<= 0)) {
$path
=
'...'
.
$path
;
last
; }
$path
= Stringify(
$node
) .
$path
; }
return
$path
; }
sub
find_insertion_point {
my
(
$self
,
$qname
,
$has_opened
) =
@_
;
closeText_internal(
$self
);
my
$cur_qname
=
$$self
{model}->getNodeQName(
$$self
{node});
my
$inter
;
if
(canContain(
$self
,
$cur_qname
,
$qname
)) {
return
$$self
{node}; }
elsif
((
$inter
= canContainIndirect(
$self
,
$cur_qname
,
$qname
))
&& (
$inter
ne
$qname
) && (
$inter
ne
$cur_qname
)) {
Debug(
"Need intermediate $inter to open $qname"
)
if
$LaTeXML::DEBUG
{document};
openElement(
$self
,
$inter
,
_autoopened
=> 1,
font
=> getNodeFont(
$self
,
$$self
{node}));
return
find_insertion_point(
$self
,
$qname
,
$inter
); }
elsif
(
$has_opened
) {
Error(
'malformed'
,
$qname
,
$self
,
(
$qname
eq
'#PCDATA'
?
$qname
:
'<'
.
$qname
.
'>'
) .
" failed auto-open through <$has_opened> at inadmissible <$cur_qname>"
,
"Currently in "
. getInsertionContext(
$self
));
return
$$self
{node}; }
else
{
my
(
$node
,
$closeto
) = (
$$self
{node});
while
((
$node
->nodeType != XML_DOCUMENT_NODE) && canAutoClose(
$self
,
$node
)) {
my
$parent
=
$node
->parentNode;
if
(canContainSomehow(
$self
,
$parent
,
$qname
)) {
$closeto
=
$node
;
last
; }
$node
=
$parent
; }
if
(
$closeto
) {
my
$closeto_qname
=
$$self
{model}->getNodeQName(
$closeto
);
closeNode_internal(
$self
,
$closeto
);
return
find_insertion_point(
$self
,
$qname
); }
else
{
Error(
'malformed'
,
$qname
,
$self
,
(
$qname
eq
'#PCDATA'
?
$qname
:
'<'
.
$qname
.
'>'
) .
" isn't allowed in <$cur_qname>"
,
"Currently in "
. getInsertionContext(
$self
));
return
$$self
{node}; } } }
sub
getInsertionCandidates {
my
(
$node
) =
@_
;
my
@nodes
= ();
my
$first
=
$node
;
$first
=
$first
->parentNode
if
$first
&&
$first
->getType == XML_TEXT_NODE;
my
$isCapture
=
$first
&& (
$first
->localname ||
''
) eq
'_Capture_'
;
push
(
@nodes
,
$first
)
if
$first
&&
$first
->getType != XML_DOCUMENT_NODE && !
$isCapture
;
my
$do_sibs
=
$node
->getType == XML_TEXT_NODE;
while
(
$node
&& (
$node
->nodeType != XML_DOCUMENT_NODE)) {
my
$n
=
$node
;
if
((
$node
->localname ||
''
) eq
'_Capture_'
) {
push
(
@nodes
, element_nodes(
$node
)); }
else
{
push
(
@nodes
,
$node
); }
if
(
$do_sibs
&& (
$n
=
$node
->previousSibling)) {
$node
=
$n
;
}
else
{
$node
=
$node
->parentNode;
my
$t
=
$node
->localname ||
''
;
$do_sibs
= 0
unless
(
$t
eq
'p'
) || (
$t
eq
'para'
);
} }
push
(
@nodes
,
$first
)
if
$isCapture
;
return
@nodes
; }
sub
floatToElement {
my
(
$self
,
$qname
,
$closeifpossible
) =
@_
;
my
@candidates
= getInsertionCandidates(
$$self
{node});
my
$closeable
= 1;
if
(
@candidates
&& canContain(
$self
,
$candidates
[0],
$qname
)) {
setNode(
$self
,
$candidates
[0])
if
$$self
{node}->getType == XML_TEXT_NODE;
return
$candidates
[0]; }
while
(
@candidates
&& !canContain(
$self
,
$candidates
[0],
$qname
)) {
$closeable
&&= canAutoClose(
$self
,
$candidates
[0]);
shift
(
@candidates
); }
if
(
my
$n
=
shift
(
@candidates
)) {
if
(
$closeifpossible
&&
$closeable
) {
closeToNode(
$self
,
$n
); }
else
{
my
$savenode
=
$$self
{node};
setNode(
$self
,
$n
);
Debug(
"Floating from "
. Stringify(
$savenode
) .
" to "
. Stringify(
$n
) .
" for $qname"
)
if
(
$$savenode
ne
$$n
) &&
$LaTeXML::DEBUG
{document};
return
$savenode
; } }
else
{
Warn(
'malformed'
,
$qname
,
$self
,
"No open node can contain element '$qname'"
,
getInsertionContext(
$self
))
unless
canContainSomehow(
$self
,
$$self
{node},
$qname
); }
return
; }
sub
floatToAttribute {
my
(
$self
,
$key
) =
@_
;
my
@candidates
= getInsertionCandidates(
$$self
{node});
while
(
@candidates
&& !canHaveAttribute(
$self
,
$candidates
[0],
$key
)) {
shift
(
@candidates
); }
if
(
my
$n
=
shift
(
@candidates
)) {
my
$savenode
=
$$self
{node};
setNode(
$self
,
$n
);
return
$savenode
; }
else
{
Warn(
'malformed'
,
$key
,
$self
,
"No open node can get attribute '$key'"
,
getInsertionContext(
$self
));
return
; } }
sub
floatToLabel {
my
(
$self
) =
@_
;
my
$key
=
'labels'
;
my
$start
=
$$self
{node};
if
(
$start
&& (
$start
->nodeType == XML_ELEMENT_NODE)) {
if
(
my
$last
=
$start
->lastChild) {
$start
=
$last
; } }
my
@ancestors
=
grep
{
$_
->nodeType == XML_ELEMENT_NODE } getInsertionCandidates(
$start
);
my
@candidates
=
@ancestors
;
while
(
@candidates
&& !(canHaveAttribute(
$self
,
$candidates
[0],
$key
)
&&
$candidates
[0]->hasAttribute(
'xml:id'
))) {
shift
(
@candidates
); }
my
$node
=
shift
(
@candidates
);
if
(!
$node
) {
my
$sib
=
$ancestors
[0] &&
$ancestors
[0]->lastChild;
if
(
$sib
&& canHaveAttribute(
$self
,
$sib
,
$key
)
&&
$sib
->hasAttribute(
'xml:id'
)) {
$node
=
$sib
; }
elsif
(
@ancestors
) {
$node
=
$ancestors
[-1]; } }
if
(
$node
) {
my
$savenode
=
$$self
{node};
setNode(
$self
,
$node
);
return
$savenode
; }
else
{
Warn(
'malformed'
,
$key
,
$self
,
"No open node with an xml:id can get attribute '$key'"
,
getInsertionContext(
$self
));
return
; } }
sub
openText_internal {
my
(
$self
,
$text
) =
@_
;
return
$$self
{node}
unless
defined
$text
;
my
(
$qname
,
$p
,
$pp
);
if
(
$$self
{node}->nodeType == XML_TEXT_NODE) {
Debug(
"Appending text \"$text\" to "
. Stringify(
$$self
{node}))
if
$LaTeXML::DEBUG
{document};
my
$parent
=
$$self
{node}->parentNode;
if
(
$LaTeXML::BOX
&&
$parent
->getAttribute(
'_autoopened'
)) {
appendTextBox(
$self
,
$parent
,
$LaTeXML::BOX
); }
$$self
{node}->appendData(
$text
); }
elsif
((
$p
=
$$self
{node}->lastChild) && (
$p
->nodeType == XML_COMMENT_NODE)
&& (
$pp
=
$p
->previousSibling) && (
$pp
->nodeType == XML_TEXT_NODE)) {
$$self
{node}->insertAfter(
$pp
,
$p
);
$$self
{node} =
$pp
;
openText_internal(
$self
,
$text
); }
elsif
((
$text
=~ /\S/)
|| canContain(
$self
,
$$self
{node},
'#PCDATA'
)) { # or text allowed here
my
$point
= find_insertion_point(
$self
,
'#PCDATA'
);
my
$node
=
$$self
{document}->createTextNode(
$text
);
if
(
$point
->getAttribute(
'_autoopened'
)) {
appendTextBox(
$self
,
$point
,
$LaTeXML::BOX
); }
Debug(
"Inserting text node for \"$text\" into "
. Stringify(
$point
))
if
$LaTeXML::DEBUG
{document};
$point
->appendChild(
$node
);
setNode(
$self
,
$node
); }
return
$$self
{node}; }
sub
appendTextBox {
my
(
$self
,
$node
,
$box
) =
@_
;
my
$origbox
= getNodeBox(
$self
,
$node
);
if
(
$origbox
&& (
$box
ne
$origbox
)) {
my
$newbox
= List(
$origbox
,
$box
);
setNodeBox(
$self
,
$node
,
$newbox
);
my
$p
=
$node
;
while
((
$p
=
$p
->parentNode) && (
$p
->nodeType == XML_ELEMENT_NODE)
&&
$p
->getAttribute(
'_autoopened'
)
&& ((getNodeBox(
$self
,
$p
) ||
''
) eq
$origbox
)) {
setNodeBox(
$self
,
$p
,
$newbox
); } }
return
; }
sub
openMathText_internal {
my
(
$self
,
$string
) =
@_
;
my
$node
=
$$self
{node};
my
$font
= getNodeFont(
$self
,
$node
);
$node
->appendText(
$string
);
if
(!
$STATE
->lookupValue(
'NOMATHPARSE'
)) {
applyMathLigatures(
$self
,
$node
); }
return
$node
; }
sub
applyMathLigatures {
my
(
$self
,
$node
) =
@_
;
if
(
my
$ligatures
=
$STATE
->lookupValue(
'MATH_LIGATURES'
)) {
my
@ligatures
=
@$ligatures
;
while
(
@ligatures
) {
my
$matched
= 0;
foreach
my
$ligature
(
@ligatures
) {
if
(applyMathLigature(
$self
,
$node
,
$ligature
)) {
@ligatures
=
grep
{
$_
ne
$ligature
}
@ligatures
;
$matched
= 1;
last
; } }
return
unless
$matched
; } }
return
; }
sub
applyMathLigature {
my
(
$self
,
$node
,
$ligature
) =
@_
;
my
(
$nmatched
,
$newstring
,
%attr
) = &{
$$ligature
{matcher} }(
$self
,
$node
);
if
(
$nmatched
) {
my
@boxes
= (getNodeBox(
$self
,
$node
));
$node
->firstChild->setData(
$newstring
);
my
$prev
=
$node
;
for
(
my
$i
= 0 ;
$i
<
$nmatched
- 1 ;
$i
++) {
my
$remove
=
$prev
->previousSibling;
unshift
(
@boxes
, getNodeBox(
$self
,
$remove
));
if
(
$remove
->nodeType == XML_COMMENT_NODE) {
$prev
=
$remove
; }
else
{ removeNode(
$self
,
$remove
); } }
if
(
scalar
(
@boxes
) > 1) {
setNodeBox(
$self
,
$node
, List(
@boxes
,
mode
=>
'math'
)); }
foreach
my
$key
(
sort
keys
%attr
) {
my
$value
=
$attr
{
$key
};
if
(
defined
$value
) {
$node
->setAttribute(
$key
=>
$value
); }
else
{
$node
->removeAttribute(
$key
); } }
return
1; }
else
{
return
; } }
sub
closeText_internal {
my
(
$self
) =
@_
;
my
$node
=
$$self
{node};
if
(
$node
->nodeType == XML_TEXT_NODE) {
my
$parent
=
$node
->parentNode;
my
$font
= getNodeFont(
$self
,
$parent
);
my
$string
=
$node
->data;
my
$ostring
=
$string
;
my
$fonttest
;
if
(
my
$ligatures
=
$STATE
->lookupValue(
'TEXT_LIGATURES'
)) {
foreach
my
$ligature
(
@$ligatures
) {
next
if
(
$fonttest
=
$$ligature
{fontTest}) && !
&$fonttest
(
$font
);
$string
= &{
$$ligature
{code} }(
$string
); } }
$node
->setData(
$string
)
unless
$string
eq
$ostring
;
Debug(
"LIGATURE $ostring => $string"
)
if
$LaTeXML::DEBUG
{document} && (
$string
ne
$ostring
);
$$self
{node} =
$parent
;
return
$parent
; }
else
{
return
$node
; } }
sub
closeNode_internal {
my
(
$self
,
$node
) =
@_
;
my
$closeto
=
$node
->parentNode;
my
$n
= closeText_internal(
$self
);
while
(
$n
->nodeType == XML_ELEMENT_NODE) {
closeElementAt(
$self
,
$n
);
autoCollapseChildren(
$self
,
$n
);
last
if
$node
->isSameNode(
$n
);
$n
=
$n
->parentNode; }
Debug(
"closeNode(int) "
. Stringify(
$$self
{node}))
if
$LaTeXML::DEBUG
{document};
setNode(
$self
,
$closeto
);
return
$$self
{node}; }
our
%non_mergeable_attributes
=
map
{
$_
=> 1; }
qw(about aboutlabelref aboutidref
resource resourcelabelref resourceidref
property rel rev tyupeof datatype content
data datamimetype dataencoding
framed)
;
sub
autoCollapseChildren {
my
(
$self
,
$node
) =
@_
;
my
$model
=
$$self
{model};
my
$qname
=
$model
->getNodeQName(
$node
);
my
@c
;
if
((
$qname
ne
'ltx:_Capture_'
)
&& (
scalar
(
@c
=
$node
->childNodes) == 1)
&& (
$model
->getNodeQName(
$c
[0]) eq
$FONT_ELEMENT_NAME
)
&& !(
grep
{ !
$model
->canHaveAttribute(
$qname
,
$_
) }
(
'font'
,
grep
{ /^[^_]/ }
map
{
$_
->nodeName }
$c
[0]->attributes))
&& !(
grep
{
$non_mergeable_attributes
{
$_
->nodeName }; }
$c
[0]->attributes)
&& !
$c
[0]->hasAttribute(
'_force_font'
)) {
my
$c
=
$c
[0];
setNodeFont(
$self
,
$node
, getNodeFont(
$self
,
$c
));
removeNode(
$self
,
$c
);
foreach
my
$gc
(
$c
->childNodes) {
$node
->appendChild(
$gc
);
recordNodeIDs(
$self
,
$node
); }
mergeAttributes(
$self
,
$c
,
$node
); }
return
; }
our
%merge_attribute_spacejoin
=
map
{
$_
=> 1; }
qw(class lists inlist labels)
;
our
%merge_attribute_semicolonjoin
=
map
{
$_
=> 1; }
qw(cssstyle)
;
our
%merge_attribute_sumlength
=
map
{
$_
=> 1; }
qw(xoffset yoffset lpadding rpadding xtranslate ytranslate)
;
sub
mergeAttributes {
my
(
$self
,
$from
,
$to
,
$override
) =
@_
;
foreach
my
$attr
(
$from
->attributes()) {
if
(
$attr
->nodeType == XML_ATTRIBUTE_NODE) {
my
$key
=
$attr
->nodeName;
my
$val
=
$attr
->getValue;
if
(
$key
eq
'xml:id'
) {
if
(!
$to
->hasAttribute(
$key
) || (
$override
&&
$$override
{
$key
})) {
unRecordID(
$self
,
$val
);
$val
= recordID(
$self
,
$val
,
$to
);
$to
->setAttribute(
$key
,
$val
); } }
elsif
(
$merge_attribute_spacejoin
{
$key
}) {
addSSValues(
$self
,
$to
,
$key
,
$val
); }
elsif
(
$merge_attribute_semicolonjoin
{
$key
}) {
my
$oldval
=
$to
->getAttribute(
$key
);
if
(
$oldval
) {
$to
->setAttribute(
$key
,
$oldval
.
'; '
.
$val
); }
else
{
$to
->setAttribute(
$key
,
$val
); } }
elsif
(
$merge_attribute_sumlength
{
$key
}) {
if
(
my
$val2
=
$to
->getAttribute(
$key
)) {
my
$v1
=
$val
=~ /^([\+\-\d\.]*)pt$/ && $1;
my
$v2
=
$val2
=~ /^([\+\-\d\.]*)pt$/ && $1;
$to
->setAttribute(
$key
=> (
$v1
+
$v2
) .
'pt'
); }
else
{
$to
->setAttribute(
$key
=>
$val
); } }
elsif
((!
$to
->hasAttribute(
$key
)) || (
$override
&&
$$override
{
$key
})) {
if
(
my
$ns
=
$attr
->namespaceURI) {
$to
->setAttributeNS(
$ns
,
$attr
->name,
$val
); }
else
{
$to
->setAttribute(
$attr
->localname,
$val
); } } } }
return
; }
sub
makeError {
my
(
$self
,
$type
,
$content
) =
@_
;
my
$savenode
=
undef
;
$savenode
= floatToElement(
$self
,
'ltx:ERROR'
)
unless
isOpenable(
$self
,
'ltx:ERROR'
);
openElement(
$self
,
'ltx:ERROR'
,
class
=> ToString(
$type
));
openText_internal(
$self
, ToString(
$content
));
closeElement(
$self
,
'ltx:ERROR'
);
setNode(
$self
,
$savenode
)
if
$savenode
;
return
; }
sub
setAttribute {
my
(
$self
,
$node
,
$key
,
$value
) =
@_
;
return
if
(
ref
$value
) && ((!blessed(
$value
)) || !
$value
->can(
'toAttribute'
));
$value
=
$value
->toAttribute
if
ref
$value
;
if
((
defined
$value
) && (
$value
ne
''
)) {
if
(
$key
eq
'xml:id'
) {
$value
= recordID(
$self
,
$value
,
$node
);
$node
->setAttributeNS(
$LaTeXML::Common::XML::XML_NS
,
'id'
,
$value
); }
elsif
(
$key
!~ /:/) {
my
$model
=
$$self
{model};
my
$qname
=
$model
->getNodeQName(
$node
);
if
(
$model
->canHaveAttribute(
$qname
,
$key
) ||
$key
=~ /^_/) {
$node
->setAttribute(
$key
=>
$value
); } }
else
{
my
(
$ns
,
$name
) =
$$self
{model}->decodeQName(
$key
);
if
(
$ns
) {
my
$prefix
=
$node
->lookupNamespacePrefix(
$ns
);
if
(!
$prefix
) {
$prefix
=
$$self
{model}->getDocumentNamespacePrefix(
$ns
, 1);
getDocument(
$self
)->documentElement->setNamespace(
$ns
,
$prefix
, 0); }
if
(
$prefix
eq
'#default'
) { # Probably shouldn't happen...?
$node
->setAttribute(
$name
=>
$value
); }
else
{
$node
->setAttributeNS(
$ns
,
"$prefix:$name"
=>
$value
); } }
else
{
$node
->setAttribute(
$name
=>
$value
); } } }
return
; }
sub
addSSValues {
my
(
$self
,
$node
,
$key
,
$values
) =
@_
;
$values
=
$values
->toAttribute
if
ref
$values
;
if
((
defined
$values
) && (
$values
ne
''
)) {
my
@values
=
split
(/\s+/,
$values
);
if
(
my
$oldvalues
=
$node
->getAttribute(
$key
)) {
my
@old
=
split
(/\s+/,
$oldvalues
);
foreach
my
$new
(
@values
) {
push
(
@old
,
$new
)
unless
grep
{
$_
eq
$new
}
@old
; }
setAttribute(
$self
,
$node
,
$key
=>
join
(
' '
,
sort
@old
)); }
else
{
setAttribute(
$self
,
$node
,
$key
=>
join
(
' '
,
sort
@values
)); } }
return
; }
sub
addClass {
my
(
$self
,
$node
,
$value
) =
@_
;
return
addSSValues(
$self
,
$node
,
class
=>
$value
); }
sub
removeSSValues {
my
(
$self
,
$node
,
$key
,
$values
) =
@_
;
$values
=
$values
->toAttribute
if
ref
$values
;
my
@to_remove
=
split
(/\s+/, (
$values
||
''
));
return
unless
@to_remove
;
if
(
my
$current_values
=
$node
->getAttribute(
$key
)) {
my
@current_values
=
split
(/\s+/,
$current_values
);
my
@updated
= ();
foreach
my
$current_value
(
@current_values
) {
push
(
@updated
,
$current_value
)
unless
grep
{
$_
eq
$current_value
}
@to_remove
; }
if
(
@updated
) {
setAttribute(
$self
,
$node
,
$key
=>
join
(
' '
,
sort
@updated
)); }
else
{
$node
->removeAttribute(
$key
); } }
return
; }
sub
removeClass {
my
(
$self
,
$node
,
$value
) =
@_
;
removeSSValues(
$self
,
$node
,
class
=>
$value
);
return
; }
sub
recordID {
my
(
$self
,
$id
,
$node
) =
@_
;
if
(
my
$prev
=
$$self
{idstore}{
$id
}) {
if
(!
$node
->isSameNode(
$prev
)) {
my
$badid
=
$id
;
$id
= modifyID(
$self
,
$id
);
Info(
'malformed'
,
'id'
,
$node
,
"Duplicated attribute xml:id"
,
"Using id='$id' on "
. Stringify(
$node
),
"id='$badid' already set on "
. Stringify(
$prev
)); } }
$$self
{idstore}{
$id
} =
$node
;
return
$id
; }
sub
unRecordID {
my
(
$self
,
$id
) =
@_
;
delete
$$self
{idstore}{
$id
};
return
; }
sub
recordNodeIDs {
my
(
$self
,
$node
) =
@_
;
foreach
my
$idnode
(findnodes(
$self
,
'descendant-or-self::*[@xml:id]'
,
$node
)) {
if
(
my
$id
=
$idnode
->getAttribute(
'xml:id'
)) {
my
$newid
= recordID(
$self
,
$id
,
$idnode
);
$idnode
->setAttribute(
'xml:id'
=>
$newid
)
if
$newid
ne
$id
; } }
return
; }
sub
unRecordNodeIDs {
my
(
$self
,
$node
) =
@_
;
foreach
my
$idnode
(findnodes(
$self
,
'descendant-or-self::*[@xml:id]'
,
$node
)) {
if
(
my
$id
=
$idnode
->getAttribute(
'xml:id'
)) {
unRecordID(
$self
,
$id
); } }
return
; }
sub
modifyID {
my
(
$self
,
$id
) =
@_
;
if
(
my
$prev
=
$$self
{idstore}{
$id
}) {
my
$badid
=
$id
;
if
(!
$LaTeXML::Core::Document::ID_SUFFIX
||
$$self
{idstore}{
$id
=
$badid
.
$LaTeXML::Core::Document::ID_SUFFIX
}) {
foreach
my
$s1
(1 .. 26 * 26 * 26) {
return
$id
unless
$$self
{idstore}{
$id
=
$badid
. radix_alpha(
$s1
) }; }
Error(
'malformed'
,
'id'
,
$self
,
"Automatic incrementing of ID counters failed"
,
"Last alternative for '$id' is '$badid'"
); } }
return
$id
; }
sub
lookupID {
my
(
$self
,
$id
) =
@_
;
return
$$self
{idstore}{
$id
}; }
sub
markXMNodeVisibility {
my
(
$self
) =
@_
;
my
@xmath
= findnodes(
$self
,
'//ltx:XMath/*'
);
foreach
my
$math
(
@xmath
) {
foreach
my
$node
(findnodes(
$self
,
'descendant-or-self::*[@_pvis or @_cvis]'
,
$math
)) {
$node
->removeAttribute(
'_pvis'
);
$node
->removeAttribute(
'_cvis'
); } }
foreach
my
$math
(
@xmath
) {
markXMNodeVisibility_aux(
$self
,
$math
, 1, 1); }
return
; }
sub
markXMNodeVisibility_aux {
no
warnings
'recursion'
;
my
(
$self
,
$node
,
$cvis
,
$pvis
) =
@_
;
my
$qname
= getNodeQName(
$self
,
$node
);
return
if
(!
$cvis
||
$node
->getAttribute(
'_cvis'
)) && (!
$pvis
||
$node
->getAttribute(
'_pvis'
));
$pvis
= 1
if
$cvis
&& (
$qname
eq
'ltx:XMArg'
);
$node
->setAttribute(
'_cvis'
=> 1)
if
$cvis
;
$node
->setAttribute(
'_pvis'
=> 1)
if
$pvis
;
if
(
$qname
eq
'ltx:XMDual'
) {
my
(
$c
,
$p
) = element_nodes(
$node
);
markXMNodeVisibility_aux(
$self
,
$c
, 1, 0)
if
$cvis
;
markXMNodeVisibility_aux(
$self
,
$p
, 0, 1)
if
$pvis
; }
elsif
(
$qname
eq
'ltx:XMRef'
) {
my
$id
=
$node
->getAttribute(
'idref'
);
if
(!
$id
) {
my
$key
=
$node
->getAttribute(
'_xmkey'
);
Warn(
'expected'
,
'id'
,
$self
,
"Missing idref on ltx:XMRef"
,
(
$key
? (
"_xmkey is $key"
) : ()));
return
; }
my
$reffed
= lookupID(
$self
,
$id
);
if
(!
$reffed
) {
Warn(
'expected'
,
'node'
,
$self
,
"No node found with id=$id (referred to from ltx:XMRef)"
);
return
; }
markXMNodeVisibility_aux(
$self
,
$reffed
,
$cvis
,
$pvis
); }
else
{
foreach
my
$child
(element_nodes(
$node
)) {
markXMNodeVisibility_aux(
$self
,
$child
,
$cvis
,
$pvis
); } }
return
; }
sub
pruneXMDuals {
my
(
$self
) =
@_
;
markXMNodeVisibility(
$self
);
foreach
my
$dual
(
reverse
findnodes(
$self
,
'descendant-or-self::ltx:XMDual'
)) {
my
(
$content
,
$presentation
) = element_nodes(
$dual
);
if
(!findnode(
$self
,
'descendant-or-self::*[@_pvis or @_cvis]'
,
$content
)) {
collapseXMDual(
$self
,
$dual
,
$presentation
); }
elsif
(!findnode(
$self
,
'descendant-or-self::*[@_pvis or @_cvis]'
,
$presentation
)) {
collapseXMDual(
$self
,
$dual
,
$content
); }
else
{
compactXMDual(
$self
,
$dual
,
$content
,
$presentation
); } }
return
; }
our
$content_transfer_overrides
= {
map
{ (
$_
=> 1) }
qw(decl_id meaning name omcd)
};
our
$dual_transfer_overrides
= {
%$content_transfer_overrides
,
map
{ (
$_
=> 1) }
qw(xml:id role)
};
sub
compactXMDual {
my
(
$self
,
$dual
,
$content
,
$presentation
) =
@_
;
my
$c_name
= getNodeQName(
$self
,
$content
);
my
$p_name
= getNodeQName(
$self
,
$presentation
);
if
((
$c_name
eq
'ltx:XMTok'
) && (
$p_name
eq
'ltx:XMTok'
)) {
mergeAttributes(
$self
,
$content
,
$presentation
,
$content_transfer_overrides
);
mergeAttributes(
$self
,
$dual
,
$presentation
,
$dual_transfer_overrides
);
replaceNode(
$self
,
$dual
,
$presentation
);
return
; }
return
if
(
$c_name
ne
'ltx:XMApp'
) || (
$p_name
ne
'ltx:XMApp'
);
my
@content_args
= element_nodes(
$content
);
my
@pres_args
= element_nodes(
$presentation
);
return
if
scalar
(
@content_args
) !=
scalar
(
@pres_args
);
my
@new_args
= ();
while
((
my
$c_arg
=
shift
(
@content_args
)) and (
my
$p_arg
=
shift
(
@pres_args
))) {
my
$c_idref
=
$c_arg
->getAttribute(
'idref'
);
if
(
$c_idref
&& (
$c_idref
eq (
$p_arg
->getAttribute(
'xml:id'
) ||
''
))) {
push
@new_args
,
$p_arg
;
next
; }
my
$p_idref
=
$p_arg
->getAttribute(
'idref'
);
if
(
$p_idref
&& (
$p_idref
eq (
$c_arg
->getAttribute(
'xml:id'
) ||
''
))) {
push
@new_args
,
$c_arg
;
next
; }
if
(getNodeQName(
$self
,
$c_arg
) ne
'ltx:XMTok'
) {
return
; }
else
{
push
(
@new_args
, [
$c_arg
,
$p_arg
]); } }
my
$compact_apply
= openElementAt(
$self
,
$dual
->parentNode,
'ltx:XMApp'
);
for
my
$n_arg
(
@new_args
) {
if
(
ref
$n_arg
eq
'ARRAY'
) {
my
(
$c_arg
,
$p_arg
) =
@$n_arg
;
mergeAttributes(
$self
,
$c_arg
,
$p_arg
,
$content_transfer_overrides
);
$n_arg
=
$p_arg
; }
$n_arg
->unbindNode;
$compact_apply
->appendChild(
$n_arg
); }
mergeAttributes(
$self
,
$dual
,
$compact_apply
,
$dual_transfer_overrides
);
replaceNode(
$self
,
$dual
,
$compact_apply
);
closeElementAt(
$self
,
$compact_apply
);
return
; }
sub
collapseXMDual {
my
(
$self
,
$dual
,
$branch
) =
@_
;
if
(
my
$dualid
=
$dual
->getAttribute(
'xml:id'
)) {
unRecordID(
$self
,
$dualid
);
if
(
my
$branchid
=
$branch
->getAttribute(
'xml:id'
)) {
foreach
my
$ref
(findnodes(
$self
,
"//*[\@idref='$dualid']"
)) {
$ref
->setAttribute(
idref
=>
$branchid
); } }
else
{
$branch
->setAttribute(
'xml:id'
=>
$dualid
);
recordID(
$self
,
$dualid
=>
$branch
); } }
replaceTree(
$self
,
$branch
,
$dual
);
return
; }
sub
setNodeBox {
my
(
$self
,
$node
,
$box
) =
@_
;
return
unless
$box
;
my
$boxid
=
"$box"
;
$$self
{node_boxes}{
$boxid
} =
$box
;
return
$node
->setAttribute(
_box
=>
$boxid
); }
sub
getNodeBox {
my
(
$self
,
$node
) =
@_
;
return
unless
$node
;
my
$t
=
$node
->nodeType;
return
if
$t
!= XML_ELEMENT_NODE;
if
(
my
$boxid
=
$node
->getAttribute(
'_box'
)) {
return
$$self
{node_boxes}{
$boxid
}; } }
sub
setNodeFont {
my
(
$self
,
$node
,
$font
) =
@_
;
return
unless
ref
$font
;
my
$fontid
=
$font
->toString;
$$self
{node_fonts}{
$fontid
} =
$font
;
if
(
$node
->nodeType == XML_ELEMENT_NODE) {
$node
->setAttribute(
_font
=>
$fontid
); }
return
; }
sub
mergeNodeFontRec {
my
(
$self
,
$node
,
$font
) =
@_
;
return
unless
ref
$font
;
my
$oldfont
= getNodeFont(
$self
,
$node
);
my
%props
=
$oldfont
->purestyleChanges(
$font
);
my
@nodes
= (
$node
);
while
(
my
$n
=
shift
(
@nodes
)) {
if
(
$n
->nodeType == XML_ELEMENT_NODE) {
setNodeFont(
$self
,
$n
, getNodeFont(
$self
,
$n
)->merge(
%props
));
push
(
@nodes
,
$n
->childNodes); } }
return
; }
sub
getNodeFont {
my
(
$self
,
$node
) =
@_
;
my
$t
;
while
(
$node
&& ((
$t
=
$node
->nodeType) != XML_ELEMENT_NODE)) {
$node
=
$node
->parentNode; }
return
(
$node
&& (
$t
== XML_ELEMENT_NODE) &&
$$self
{node_fonts}{
$node
->getAttribute(
'_font'
) })
|| LaTeXML::Common::Font->textDefault(); }
sub
getNodeLanguage {
my
(
$self
,
$node
) =
@_
;
my
(
$font
,
$lang
);
while
(
$node
&& (
$node
->nodeType == XML_ELEMENT_NODE)
&& !((
$lang
=
$node
->getAttribute(
'xml:lang'
))
|| ((
$font
=
$$self
{node_fonts}{
$node
->getAttribute(
'_font'
) })
&& (
$lang
=
$font
->getLanguage)))) {
$node
=
$node
->parentNode; }
return
$lang
||
'en'
; }
sub
decodeFont {
my
(
$self
,
$fontid
) =
@_
;
return
$$self
{node_fonts}{
$fontid
} || LaTeXML::Common::Font->textDefault(); }
sub
removeNode {
my
(
$self
,
$node
) =
@_
;
if
(
$node
) {
my
$chopped
=
$$self
{node}->isSameNode(
$node
);
if
(
$node
->nodeType == XML_ELEMENT_NODE) {
if
(
my
$id
=
$node
->getAttribute(
'xml:id'
)) {
unRecordID(
$self
,
$id
); }
$chopped
||=
grep
{ removeNode_aux(
$self
,
$_
) }
$node
->childNodes; }
my
$parent
=
$node
->parentNode;
if
(
$chopped
) {
setNode(
$self
,
$parent
); }
$parent
->removeChild(
$node
);
}
return
$node
; }
sub
removeNode_aux {
my
(
$self
,
$node
) =
@_
;
my
$chopped
=
$$self
{node}->isSameNode(
$node
);
if
(
$node
->nodeType == XML_ELEMENT_NODE) {
if
(
my
$id
=
$node
->getAttribute(
'xml:id'
)) {
unRecordID(
$self
,
$id
); }
$chopped
||=
grep
{ removeNode_aux(
$self
,
$_
) }
$node
->childNodes; }
return
$chopped
; }
sub
openElementAt {
my
(
$self
,
$point
,
$qname
,
%attributes
) =
@_
;
my
(
$ns
,
$tag
) =
$$self
{model}->decodeQName(
$qname
);
my
$newnode
;
my
$font
=
$attributes
{_font} ||
$attributes
{font};
my
$box
=
$attributes
{_box};
$box
=
$$self
{node_boxes}{
$box
}
if
$box
&& !
ref
$box
;
if
(
$point
->nodeType == XML_DOCUMENT_NODE) {
$$self
{model}->addSchemaDeclaration(
$self
,
$tag
);
map
{
$$self
{document}->appendChild(
$_
) } @{
$$self
{pending} };
$newnode
=
$$self
{document}->createElement(
$tag
);
recordConstructedNode(
$self
,
$newnode
);
$$self
{document}->setDocumentElement(
$newnode
);
if
(
$ns
) {
my
$prefix
=
$$self
{model}->getDocumentNamespacePrefix(
$ns
);
my
$attprefix
=
$$self
{model}->getDocumentNamespacePrefix(
$ns
, 1, 1);
if
(!
$prefix
&&
$attprefix
) {
$newnode
->setNamespace(
$ns
,
$attprefix
, 0); }
$newnode
->setNamespace(
$ns
,
$prefix
, 1); } }
else
{
$font
= getNodeFont(
$self
,
$point
)
unless
$font
;
$box
= getNodeBox(
$self
,
$point
)
unless
$box
;
$newnode
= openElement_internal(
$self
,
$point
,
$ns
,
$tag
); }
foreach
my
$key
(
sort
keys
%attributes
) {
next
if
$key
eq
'font'
;
next
if
$key
eq
'locator'
;
setAttribute(
$self
,
$newnode
,
$key
,
$attributes
{
$key
}); }
setNodeFont(
$self
,
$newnode
,
$font
)
if
$font
;
setNodeBox(
$self
,
$newnode
,
$box
)
if
$box
;
appendElementBox(
$self
,
$newnode
,
$box
)
if
$box
;
Debug(
"Inserting "
. Stringify(
$newnode
) .
" into "
. Stringify(
$point
))
if
$LaTeXML::DEBUG
{document};
afterOpen(
$self
,
$newnode
);
return
$newnode
; }
sub
appendElementBox {
my
(
$self
,
$node
,
$box
) =
@_
;
my
(
$p
,
$origbox
);
if
((
$p
=
$node
->parentNode) && (
$p
->nodeType == XML_ELEMENT_NODE)
&&
$p
->getAttribute(
'_autoopened'
)
&& (
$origbox
= getNodeBox(
$self
,
$p
)) && (
$origbox
ne
$box
)) {
my
$newbox
= List(
$origbox
,
$box
);
setNodeBox(
$self
,
$p
,
$newbox
);
while
((
$p
=
$p
->parentNode) && (
$p
->nodeType == XML_ELEMENT_NODE)
&&
$p
->getAttribute(
'_autoopened'
)
&& ((getNodeBox(
$self
,
$p
) ||
''
) eq
$origbox
)) {
setNodeBox(
$self
,
$p
,
$newbox
); } }
return
; }
sub
openElement_internal {
my
(
$self
,
$point
,
$ns
,
$tag
) =
@_
;
my
$newnode
;
if
(
$ns
) {
if
(!
defined
$point
->lookupNamespacePrefix(
$ns
)) {
getDocument(
$self
)->documentElement
->setNamespace(
$ns
,
$$self
{model}->getDocumentNamespacePrefix(
$ns
), 0); }
$newnode
=
$point
->addNewChild(
$ns
,
$tag
); }
else
{
$newnode
=
$point
->appendChild(
$$self
{document}->createElement(
$tag
)); }
recordConstructedNode(
$self
,
$newnode
);
return
$newnode
; }
sub
closeElementAt {
my
(
$self
,
$node
) =
@_
;
return
afterClose(
$self
,
$node
); }
sub
afterOpen {
my
(
$self
,
$node
) =
@_
;
my
$savenode
=
$$self
{node};
setNode(
$self
,
$node
);
my
$box
= getNodeBox(
$self
,
$node
);
map
{
&$_
(
$self
,
$node
,
$box
) } getTagActionList(
$self
,
$node
,
'afterOpen'
);
setNode(
$self
,
$savenode
);
return
$node
; }
sub
afterClose {
my
(
$self
,
$node
) =
@_
;
my
$savenode
=
$$self
{node};
my
$box
= getNodeBox(
$self
,
$node
);
map
{
&$_
(
$self
,
$node
,
$box
) } getTagActionList(
$self
,
$node
,
'afterClose'
);
setNode(
$self
,
$savenode
);
return
$node
; }
sub
appendClone {
my
(
$self
,
$node
,
@newchildren
) =
@_
;
@newchildren
=
map
{ (
$_
->nodeType == XML_DOCUMENT_FRAG_NODE ?
$_
->childNodes :
$_
) }
@newchildren
;
local
%LaTeXML::Core::Document::IDMAP
= ();
foreach
my
$child
(
@newchildren
) {
foreach
my
$idnode
(findnodes(
$self
,
'.//@xml:id'
,
$child
)) {
my
$id
=
$idnode
->getValue;
$LaTeXML::Core::Document::IDMAP
{
$id
} = modifyID(
$self
,
$id
); } }
appendClone_aux(
$self
,
$node
,
@newchildren
);
return
$node
; }
sub
appendClone_aux {
my
(
$self
,
$node
,
@newchildren
) =
@_
;
foreach
my
$child
(
@newchildren
) {
my
$type
=
$child
->nodeType;
if
(
$type
== XML_ELEMENT_NODE) {
my
$new
= openElement_internal(
$self
,
$node
,
$child
->namespaceURI,
$child
->localname);
foreach
my
$attr
(
$child
->attributes) {
if
(
$attr
->nodeType == XML_ATTRIBUTE_NODE) {
my
$key
=
$attr
->nodeName;
if
(
$key
eq
'xml:id'
) {
my
$newid
=
$LaTeXML::Core::Document::IDMAP
{
$attr
->getValue };
$newid
= recordID(
$self
,
$newid
,
$new
);
$new
->setAttribute(
$key
,
$newid
); }
elsif
(
$key
eq
'idref'
) {
my
$id
=
$attr
->getValue;
$new
->setAttribute(
$key
,
$LaTeXML::Core::Document::IDMAP
{
$id
} ||
$id
); }
elsif
(
my
$ns
=
$attr
->namespaceURI) {
$new
->setAttributeNS(
$ns
,
$attr
->name,
$attr
->getValue); }
else
{
$new
->setAttribute(
$attr
->localname,
$attr
->getValue); } }
}
afterOpen(
$self
,
$new
);
appendClone_aux(
$self
,
$new
,
$child
->childNodes);
afterClose(
$self
,
$new
); }
elsif
(
$type
== XML_TEXT_NODE) {
$node
->appendTextNode(
$child
->textContent); } }
return
$node
; }
sub
wrapNodes {
my
(
$self
,
$qname
,
@nodes
) =
@_
;
return
unless
@nodes
;
my
$leave_open
= 0;
foreach
my
$n
(
@nodes
) {
if
(isOpen(
$self
,
$n
)) {
$leave_open
= 1;
last
; } }
my
$model
=
$$self
{model};
my
$parent
=
$nodes
[0]->parentNode;
my
(
$ns
,
$tag
) =
$model
->decodeQName(
$qname
);
my
$new
= openElement_internal(
$self
,
$parent
,
$ns
,
$tag
);
afterOpen(
$self
,
$new
);
$parent
->replaceChild(
$new
,
$nodes
[0]);
if
(
my
$font
= getNodeFont(
$self
,
$parent
)) {
setNodeFont(
$self
,
$new
,
$font
); }
if
(
my
$box
= getNodeBox(
$self
,
$parent
)) {
setNodeBox(
$self
,
$new
,
$box
); }
foreach
my
$node
(
@nodes
) {
$new
->appendChild(
$node
); }
afterClose(
$self
,
$new
)
unless
$leave_open
;
return
$new
; }
sub
isOpen {
my
(
$self
,
$node
) =
@_
;
my
$current
=
$$self
{node};
if
(
$node
->isSameNode(
$current
)) {
return
1; }
else
{
foreach
my
$n
(
$node
->childNodes) {
return
1
if
isOpen(
$self
,
$n
); }
return
0; } }
sub
unwrapNodes {
my
(
$self
,
$node
) =
@_
;
return
replaceNode(
$self
,
$node
,
$node
->childNodes); }
sub
replaceNode {
my
(
$self
,
$node
,
@nodes
) =
@_
;
my
$parent
=
$node
->parentNode;
my
$c0
;
while
(
my
$c1
=
shift
(
@nodes
)) {
if
(
$c0
) {
$parent
->insertAfter(
$c1
,
$c0
); }
else
{
$parent
->replaceChild(
$c1
,
$node
); }
$c0
=
$c1
; }
removeNode(
$self
,
$node
);
return
$node
; }
sub
renameNode {
my
(
$self
,
$node
,
$newname
,
$reinsert
) =
@_
;
my
$model
=
$$self
{model};
my
(
$ns
,
$tag
) =
$model
->decodeQName(
$newname
);
my
$parent
=
$node
->parentNode;
my
$new
= openElement_internal(
$self
,
$parent
,
$ns
,
$tag
);
my
$id
;
$parent
->insertAfter(
$new
,
$node
);
foreach
my
$attr
(
$node
->attributes) {
my
$key
=
$attr
->getName;
my
$value
=
$node
->getAttribute(
$key
);
$id
=
$value
if
$key
eq
'xml:id'
;
$new
->setAttribute(
$key
,
$value
)
if
$model
->canHaveAttribute(
$newname
,
$key
); }
if
(!
$reinsert
) {
foreach
my
$child
(
$node
->childNodes) {
$new
->appendChild(
$child
); } }
else
{
my
$savenode
=
$$self
{node};
$$self
{node} =
$new
;
foreach
my
$child
(
$node
->childNodes) {
if
(
$child
->nodeType == XML_TEXT_NODE) {
openText_internal(
$self
,
$child
->data);
closeText_internal(
$self
); }
else
{
my
$point
= find_insertion_point(
$self
, getNodeQName(
$self
,
$child
));
$point
->appendChild(
$child
); } }
$$self
{node} =
$savenode
; }
afterOpen(
$self
,
$new
);
afterClose(
$self
,
$new
);
removeNode(
$self
,
$node
);
if
(
$id
) {
my
$newid
= recordID(
$self
,
$id
,
$new
);
$new
->setAttribute(
'xml:id'
=>
$newid
)
if
$newid
ne
$id
; }
return
$new
; }
sub
replaceTree {
my
(
$self
,
$new
,
$old
) =
@_
;
my
$parent
=
$old
->parentNode;
my
@following
= ();
while
(
my
$sib
=
$parent
->lastChild) {
last
if
$sib
->isSameNode(
$old
);
$parent
->removeChild(
$sib
);
unshift
(
@following
,
$sib
); }
removeNode(
$self
,
$old
);
appendTree(
$self
,
$parent
,
$new
);
my
$inserted
=
$parent
->lastChild;
map
{
$parent
->appendChild(
$_
) }
@following
;
return
$inserted
; }
sub
appendTree {
no
warnings
'recursion'
;
my
(
$self
,
$node
,
@data
) =
@_
;
foreach
my
$child
(
@data
) {
if
(
ref
$child
eq
'ARRAY'
) {
my
(
$tag
,
$attributes
,
@children
) =
@$child
;
if
(!
$tag
&& !
$attributes
) {
appendTree(
$self
,
$node
,
@children
); }
else
{
my
$new
= openElementAt(
$self
,
$node
,
$tag
, (
$attributes
?
%$attributes
: ()));
appendTree(
$self
,
$new
,
@children
);
closeElementAt(
$self
,
$new
); } }
elsif
((
ref
$child
) =~ /^XML::LibXML::/) {
my
$type
=
$child
->nodeType;
if
(
$type
== XML_ELEMENT_NODE) {
my
$tag
= getNodeQName(
$self
,
$child
);
my
%attributes
=
map
{
$_
->nodeType == XML_ATTRIBUTE_NODE ? (getNodeQName(
$self
,
$_
) =>
$_
->getValue) : () }
$child
->attributes;
if
(
my
$id
=
$attributes
{
'xml:id'
}) {
$child
->removeAttribute(
'xml:id'
);
unRecordID(
$self
,
$id
); }
my
$new
= openElementAt(
$self
,
$node
,
$tag
,
%attributes
);
appendTree(
$self
,
$new
,
$child
->childNodes);
closeElementAt(
$self
,
$new
); }
elsif
(
$type
== XML_DOCUMENT_FRAG_NODE) {
appendTree(
$self
,
$node
,
$child
->childNodes); }
elsif
(
$type
== XML_TEXT_NODE) {
$node
->appendTextNode(
$child
->textContent); }
}
elsif
((
ref
$child
) &&
$child
->isaBox) {
my
$savenode
= getNode(
$self
);
setNode(
$self
,
$node
);
absorb(
$self
,
$child
);
setNode(
$self
,
$savenode
); }
elsif
(
ref
$child
) {
Warn(
'malformed'
,
$child
,
$node
,
"Dont know how to add '$child' to document; ignoring"
); }
elsif
(
defined
$child
) {
$node
->appendTextNode(
$child
); } }
return
; }
1;