How to use global namespace definitions when creating fragments?

The root element has namespace declarations like xmlns:xlink="http://www.w3.org/1999/xlink"

... so any added node (for example appendChild

) will accept a namespace. I may add <graphic xlink:href=".."/>

because in general it is valid ... But to add a fragment I need to create a fragment with a createDocumentFragment()

.

Example:

    $tmp = $dom->createDocumentFragment();
    $ok = $tmp->appendXML('<graphic xlink:href="file123.ext"/>');

      

generates an error at startup, DOMDocumentFragment::appendXML(): namespace error : Namespace prefix xlink for href on inline-graphic is not defined

How do I say "use the DomDocument namespaces" in a method DOMDocumentFragment::appendXML()

?


NOTES AND CONTEXTS

( referred to as an answer not to post here)

+3


source to share


3 answers


It looks like it works as intended. Check bug report # 44773 . chregu@php.net says this is not a bug and is working correctly. While I would agree with the error message and other comments that since the snippet is made from DOMDocument and it defined namespaces, it should know what they are and should work without issue.

Pass the namespace to the element. It will not render to XML, which will be output, but will be read by the fragment so that it can create the attribute without any error.

$dom = new DOMDocument('1.0', 'utf-8');
$root = $dom->createElement('MyRoot');
$root->setAttributeNS('http://www.w3.org/2000/xmlns/','xmlns:xlink','http://www.w3.org/1999/xlink');
$dom->appendChild($root);

$tmp = $dom->createDocumentFragment();
$ok = $tmp->appendXML('<graphic xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="file123.ext"/>');
$dom->documentElement->appendChild($tmp);
die($dom->saveXML());

      



Output

<?xml version="1.0" encoding="utf-8"?>
<MyRoot xmlns:xlink="http://www.w3.org/1999/xlink"><graphic xlink:href="file123.ext"/></MyRoot>

      

+2


source


This is not a bug, this is actually expected behavior. Namespaces are not defined for either the XML document but rather on the element nodes. They are valid for this node and any children until they are overridden.

So if you create a document fragment, it doesn't have a parent node and now you add the XML fragment. When you search it, it cannot find the namespace definition and you will receive an error message. Depending on where in the document you intend to add it, the namespace prefix can be used for completely different namespaces.

You must define a namespace in the fragment, if the fragment is generated by the DOM it must always have all the required namespace definitions.

If you generate it as text, you can make sure the namespace definition is included in the element that needs it, or you can add a node wrapper element with all the required namespace definitions.

$dom = new DOMDocument();
$dom->loadXml('<foo/>');

$fragment = $dom->createDocumentFragment();
$fragment->appendXML(
  '<fragment xmlns:xlink="http://www.w3.org/1999/xlink">
     <graphic xlink:href="file123.ext"/>
   </fragment>'
);
foreach ($fragment->firstChild->childNodes as $child) {
  $dom->documentElement->appendChild($child->cloneNode(TRUE));
}

echo $dom->saveXML();

      

Output:



<?xml version="1.0"?>
<foo>
  <graphic xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="file123.ext"/>
</foo>

      

You can create a wrapper element from a list of namespaces. The following function will take a node element or a list of namespaces [prefix => namespace]

.

function wrapFragment($namespaces, $xml) {
  if ($namespaces instanceOf DOMElement) {
    $xpath = new DOMXpath($namespaces->ownerDocument);
    $namespaces = $xpath->evaluate('namespace::*', $namespaces);
  }
  $result = '<fragment';
  foreach ($namespaces as $key => $value) {
    if ($value instanceOf DOMNamespaceNode) {
      $prefix = $value->localName;
      $xmlns = $value->nodeValue;
    } else {
      $prefix = $key == '#default' ? '' : $key;
      $xmlns = $value;
    }
    $result .= ' '.htmlspecialchars(empty($prefix) ? 'xmlns' : 'xmlns:'.$prefix);
    $result .= '="'.htmlspecialchars($xmlns).'"';
  }
  return $result.'>'.$xml.'</fragment>';
}

echo wrapFragment(
  $dom->documentElement, '<graphic xlink:href="file123.ext"/>'
);

      

Output:



<fragment xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:xlink="http://www.w3.org/1999/xlink"><graphic xlink:href="file123.ext"/></fragment>

      

+1


source


NOTES about the question and the relationship with PHP lack of resources for fragment namespaces.

PHP error?

(after @slapyo)

There is a PHP bug # 44773 : this is not "good behavior" - a bug (!) . If you agree, add a comment there and vote here (!).


Imagine you are using a function replace_innerXML($node,$innerXML)

or any other similar context ... See the "Typical Scenarios" section below.

How to get around? Without a big regex (more $innerXML

in the example) and a slow algorithm to set each tag without declaring a namespace ... And of course at the end the appendXML()

snippet is a component of the tree, so there is no namespace because it is already at the root .. .the whole work is just to use buggy fragments appendXML()

.

Typical scenarios

(after @ThW's answer / discussion) Typical use of "blind namespace" snippet.

When a snippet is "extraterrenal" with a new namespace, ok, the snippet needs to declare itself the namespaces it uses ... But the problem that comes up in this question is NOT this exotic, is such a comma different,
PS: how do we do it, the PHP error solution is also a solution to this context.

Here, to illustrate, there are two typical uses of Fragments where there is no prior knowledge of namespaces (used by Fragment Elements) , only the fact that all were declared in DOMDocument (no need to update).

1) a XSLT call-to-php-return-fragmentXSLTProcessor::registerPHPFunctions()

;

2) "Shared DOM Library", which offers a processing method for replacing the internal XML content in a node with new XML content, which can be a content fragment. See Function replace_innerXML()

below.

function replace_innerXML(DOMNode $e, $innerXML='') {
    if ($e && ($innerXML>'' || $e->nodeValue>'')) {
        $e->nodeValue='';   
        if ($innerXML>'') {
            $tmp = $e->ownerDocument->createDocumentFragment();
            // HERE we need to INJECT namespace declarations into $innerXML
            $tmp->appendXML($innerXML);
            $e->appendChild( $tmp );
        }
        return true;
    }
    return false;
}
// This function is only illustrative, for other propuses 
//         see https://stackoverflow.com/q/26029868/287948
// Example of use:
$innerXML='nonoo <xx xx:aa="ww">uuu</xx> nono<a:yy zz:href="...">uu</a:yy>...';
replace_innerXML($someNode,$innerXML);

      

The "INJECT namespace" algorithm (see function comment) would be simple if PHP functions were offered ... But, as we insist , PHP has a bug because it doesn't offer anything (!) .

Elephantine solution

The only way (today is 2014) is "INJECT namespace" -

$innerXML = preg_replace_callback(
   "/([<\s])($namespacesJoinByPipe):([^\s>]+)/s",
   function ($m) use($namespacesAssociative) {
     $nsdecl = "xmlns:$m[2]=\"".$namespacesAssociative[$m[2]].'"';
     return ($m[1]=='<')
       ? "<$m[1]$m[2]:$m[3] $nsdecl "    // tag like "<a:yy"
       : " $nsdecl $m[1]$m[2]:$m[3]";   // attribute like " xx:aa"
   },
   $innerXML
);

      

... So the big elephant makes it that simple: just take over the existing DOMDocument namespaces .

PHP has a bug because this "big elephant" cannot be avoided ... Solution to solve "PHP error"?

The perfect PHP solution

There are many alternatives for PHP RCF to solve the problem : flag createDocumentFragment($importRootNamespaces=false)

, node link createDocumentFragment($refNamespacesNode=NULL)

, or DOMDocumentFragment::setAttributeNS() method

... All with default behavior equivalent to normal createDocumentFragment()

(no parameters).

PS: any of these solutions also helps to deal with other problems, like the first one, "... when a fragment is" extraterrenal "with a new namespace ...".

0


source







All Articles