使用registerNodeClass扩展PHP中的DomeElement
使用registerNodeClass扩展PHP中的DomeElement,php,design-patterns,dom,Php,Design Patterns,Dom,registerNodeClass非常适合在PHP中扩展各种基于DOMNode的DOM类,但我需要更深入一层 我创建了一个扩展DOMElement的extDOMElement。这在registerNodeClass中非常有效,但我希望有一些更像这样的功能: registerNodeClass(“doElement->nodeName='XYZ','extDOMXYZElement') 考虑以下XML文档,anives.XML: <animals> <dog name="
registerNodeClass
非常适合在PHP中扩展各种基于DOMNode的DOM类,但我需要更深入一层
我创建了一个扩展
DOMElement
的extDOMElement
。这在registerNodeClass
中非常有效,但我希望有一些更像这样的功能:
registerNodeClass(“doElement->nodeName='XYZ','extDOMXYZElement')
考虑以下XML文档,anives.XML:
<animals>
<dog name="fido" />
<dog name="lucky" />
<cat name="scratchy" />
<horse name="flicka" />
</animals>
输出:
狗叫了!
幸运的是狗会吠!
猫在叫!
弗利卡,马在呜呜叫
我不想把bark()、meow()和whinny()放到extdomement中,我想把它们分别放到extDOMDogElement、extdomcateelement和extdomhorselement中
我已经看过了这里的装饰和策略模式,但我不确定如何继续。当前的设置工作正常,但我更喜欢在
extdomeElement
中共享属性和方法,每个ElementName都有单独的类,这样我就可以将每个元素特定的方法和属性从主类中分离出来。编辑您显示的代码,不扩展DOMElement不是更容易吗?只需将常规的家庭元素传递给你的处理策略,例如
class AnimalProcessor
{
public function processAnimals(DOMDocument $dom) {
foreach($dom->documentElement->childNodes as $animal) {
$strategy = $animal->tagName . 'Strategy';
$strategy = new $strategy($animal);
$strategy->process();
}
}
}
$dom = new DOMDocument;
$dom->load('animals.xml');
$processor = new AnimalProcessor;
$processor->processAnimals($dom);
问题更新前的原始答案
不确定这是否是您想要的,但是如果您想要专门的domeElement,您可以简单地创建它们并直接使用它们,例如绕过
createElement
,这样您就不必注册registerNodeClass
class DogElement extends DOMElement
{
public function __construct($value)
{
parent::__construct('dog', $value);
}
}
class CatElement extends DOMElement
{
public function __construct($value)
{
parent::__construct('cat', $value);
}
}
$dom = new DOMDocument;
$dom->loadXML('<animals/>');
$dom->documentElement->appendChild(new DogElement('Sparky'));
$dom->documentElement->appendChild(new CatElement('Tinky'));
echo $dom->saveXml();
我不能确切地指出这一点,但正如已经指出的那样,您正在尝试的是一种特定的方式。
无论如何根据实际节点(类型/contents/…),可以使用在extdoElement对象上公开不同的方法。为此,extdoElement对象可以存储一个助手对象,该对象根据元素的“类型”实例化,然后将方法调用委托给该助手对象。就我个人而言,我不太喜欢这样,因为这样做并不能使文档编制、测试和调试变得更容易。如果你觉得可行的话,我可以写一个完整的例子
这当然需要评论/文档…工作正在进行中,因为我现在没有时间
<?php
$doc = new MyDOMDocument('1.0', 'iso-8859-1');
$doc->loadxml('<animals>
<Foo name="fido" />
<Bar name="lucky" />
<Foo name="scratchy" />
<Ham name="flicka" />
<Egg name="donald" />
</animals>');
$xpath = new DOMXPath($doc);
foreach( $xpath->query('//Foo') as $e ) {
echo $e->name(), ': ', $e->foo(), "\n";
}
echo "----\n";
foreach( $xpath->query('//Bar') as $e ) {
echo $e->name(), ': ', $e->bar(), "\n";
}
echo "====\n";
echo $doc->savexml();
class MyDOMElement extends DOMElement {
protected $helper;
public function getHelper() {
// lazy loading and caching the helper object
// since lookup/instantiation can be costly
if ( is_null($this->helper) ) {
$this->helper = $this->resolveHelper();
}
return $this->helper;
}
public function isType($t) {
return $this->getHelper() instanceof $t;
}
public function __call($name, $args) {
$helper = $this->getHelper();
if ( !method_exists($helper, $name) ) {
var_dump($name, $args, $helper);
throw new Exception('yaddayadda');
}
return call_user_func_array( array($this->helper, $name), $args);
}
public function releaseHelper() {
// you might want to consider something like this
// to help php with the circular references
// ...or maybe not, haven't tested the impact circual references have on php's gc
$this->helper = null;
}
protected function resolveHelper() {
// this is hardcored only for brevity's sake
// add any kind of lookup/factory/... you like
$rv = null;
switch( $this->tagName ) {
case 'Foo':
case 'Bar':
$cn = "DOMHelper".$this->tagName;
return new $cn($this);
default:
return new DOMHelper($this);
break;
}
}
}
class MyDOMDocument extends DOMDocument {
public function __construct($version=null,$encoding=null) {
parent::__construct($version,$encoding);
$this->registerNodeClass('DOMElement', 'MyDOMElement');
}
}
class DOMHelper {
protected $node;
public function __construct(DOMNode $node) {
$this->node = $node;
}
public function name() { return $this->node->getAttribute("name"); }
}
class DOMHelperFoo extends DOMHelper {
public function foo() {
echo 'foo';
$this->node->appendChild( $this->node->ownerDocument->createElement('action', 'something'));
}
}
class DOMHelperBar extends DOMHelper {
public function bar() {
echo 'bar';
$this->node->setAttribute('done', '1');
}
}
query('//Foo')作为$e){
echo$e->name(),“:”,$e->foo(),“\n”;
}
回显“---\n”;
foreach($xpath->query('//Bar')作为$e){
回显$e->name(),“:”,$e->bar(),“\n”;
}
回显“==\n”;
echo$doc->savexml();
类MyDomeElement扩展了DomeElement{
受保护的$helper;
公共函数getHelper(){
//延迟加载和缓存辅助对象
//因为查找/实例化可能代价高昂
如果(为空($this->helper)){
$this->helper=$this->resolvehlper();
}
返回$this->helper;
}
公共函数isType($t){
返回$t的$this->getHelper()实例;
}
公共函数调用($name,$args){
$helper=$this->getHelper();
如果(!方法_存在($helper,$name)){
变量转储($name、$args、$helper);
抛出新异常('yaddayada');
}
返回call\u user\u func\u数组(数组($this->helper,$name),$args);
}
公共函数releaseheloper(){
你可能想考虑这样的事情。
//帮助php使用循环引用
//…或者可能不是,还没有测试循环引用对php的gc的影响
$this->helper=null;
}
受保护的函数resolveHelper(){
//这只是为了简洁起见而精雕细琢的
//添加任何您喜欢的查找/工厂/…类型
$rv=null;
开关($this->tagName){
案例“Foo”:
“酒吧”一案:
$cn=“DOMHelper”。$this->tagName;
返回新的$cn($this);
违约:
返回新的DOMHelper($this);
打破
}
}
}
类MyDOMDocument扩展了DOMDocument{
公共函数构造($version=null,$encoding=null){
父项::__构造($version,$encoding);
$this->registerNodeClass('DomeElement','MyDomeElement');
}
}
类DOMHelper{
受保护的$节点;
公共函数构造(DOMNode$node){
$this->node=$node;
}
公共函数名(){return$this->node->getAttribute(“name”);}
}
类DOMHelperFoo扩展了DOMHelper{
公共功能foo(){
回声“foo”;
$this->node->appendChild($this->node->ownerDocument->createElement('action','something');
}
}
类DOMHelperBar扩展了DOMHelper{
公共功能条(){
回声“条”;
$this->node->setAttribute('done','1');
}
}
印刷品
fido: foo
scratchy: foo
----
lucky: bar
====
<?xml version="1.0"?>
<animals>
<Foo name="fido"><action>something</action></Foo>
<Bar name="lucky" done="1"/>
<Foo name="scratchy"><action>something</action></Foo>
<Ham name="flicka"/>
<Egg name="donald"/>
</animals>
fido:foo
抓痒:福
----
幸运儿:酒吧
====
某物
某物
我也有同样的问题。我的解决方案是基于XMLReader扩展编写自己的解析器。由此产生的AdvancedParser类对我来说工作得非常好。可以为每个元素名称注册一个单独的元素类。通过扩展AdvancedParser类并覆盖getClassForElement()方法,还可以基于元素名动态计算所需类的名称
/**
* Specialized Xml parser with element based class registry.
*
* This class uses the XMLReader extension for document parsing and creates
* a DOM tree with individual DOMElement subclasses for each element type.
*
* @author Andreas Traber < a.traber (at) rivo-systems (dot) com >
*
* @since April 21, 2012
* @package XML
*/
class AdvancedParser
{
/**
* Map with registered classes.
* @var array
*/
protected $_elementClasses = array();
/**
* Default class for unknown elements.
* @var string
*/
protected $_defaultElementClass = 'DOMElement';
/**
* The reader for Xml parsing.
* @var XMLReader
*/
protected $_reader;
/**
* The document object.
* @var DOMDocument
*/
protected $_document;
/**
* The current parsing element.
* @var DOMElement
*/
protected $_currentElement;
/**
* Gets the fallback class for unknown elements.
*
* @return string
*/
public function getDefaultElementClass()
{
return $this->_defaultElementClass;
}
/**
* Sets the fallback class for unknown elements.
*
* @param string $class
* @return void
* @throws Exception $class is not a subclass of DOMElement.
*/
public function setDefaultElementClass($class)
{
switch (true) {
case $class === null:
$this->_defaultElementClass = 'DOMElement';
break;
case !$class instanceof DOMElement:
throw new Exception($class.' must be a subclass of DOMElement');
default:
$this->_defaultElementClass = $class;
}
}
/**
* Registers the class for a specified element name.
*
* @param string $elementName.
* @param string $class.
* @return void
* @throws Exception $class is not a subclass of DOMElement.
*/
public function registerElementClass($elementName, $class)
{
switch (true) {
case $class === null:
unset($this->_elementClasses[$elementName]);
break;
case !$class instanceof DOMElement:
throw new Exception($class.' must be a subclass of DOMElement');
default:
$this->_elementClasses[$elementName] = $class;
}
}
/**
* Gets the class for a given element name.
*
* @param string $elementName
* @return string
*/
public function getClassForElement($elementName)
{
return $this->_elementClasses[$elementName]
? $this->_elementClasses[$elementName]
: $this->_defaultElementClass;
}
/**
* Parse Xml Data from string.
*
* @see XMLReader::XML()
*
* @param string $source String containing the XML to be parsed.
* @param string $encoding The document encoding or NULL.
* @param string $options A bitmask of the LIBXML_* constants.
* @return DOMDocument The created DOM tree.
*/
public function parseString($source, $encoding = null, $options = 0)
{
$this->_reader = new XMLReader();
$this->_reader->XML($source, $encoding, $options);
return $this->_parse();
}
/**
* Parse Xml Data from file.
*
* @see XMLReader::open()
*
* @param string $uri URI pointing to the document.
* @param string $encoding The document encoding or NULL.
* @param string $options A bitmask of the LIBXML_* constants.
* @return DOMDocument The created DOM tree.
*/
public function parseFile($uri, $encoding = null, $options = 0)
{
$this->_reader = new XMLReader();
$this->_reader->open($uri, $encoding, $options);
return $this->_parse();
}
/**
* The parser.
*
* @return DOMDocument The created DOM tree.
*/
protected function _parse()
{
$this->_document = new DOMDocument('1.0', 'utf-8');
$this->_document->_elements = array(); // keep references to elements
$this->_currentElement = $this->_document;
while ($this->_reader->read()) {
switch ($this->_reader->nodeType) {
case XMLReader::ELEMENT:
$this->_reader->isEmptyElement
? $this->_addElement()
: $this->_currentElement = $this->_addElement();
break;
case XMLReader::END_ELEMENT:
$this->_currentElement = $this->_currentElement->parentNode;
break;
case XMLReader::CDATA:
$this->_currentElement->appendChild(
$this->_document->createCDATASection($this->_reader->value)
);
break;
case XMLReader::TEXT:
case XMLReader::SIGNIFICANT_WHITESPACE:
$this->_currentElement->appendChild(
$this->_document->createTextNode($this->_reader->value)
);
break;
case XMLReader::COMMENT:
$this->_currentElement->appendChild(
$this->_document->createComment($this->_reader->value)
);
break;
}
}
$this->_reader->close();
return $this->_document;
}
/**
* Adds the current element into the DOM tree.
*
* @return DOMElement The added element.
*/
protected function _addElement()
{
$element = $this->_createElement();
// It's important to keep a reference to each element.
// Elements without any reference were destroyed by the
// garbage collection and loses their type.
$this->_document->_elements[] = $element;
$this->_currentElement->appendChild($element);
$this->_addAttributes($element);
return $element;
}
/**
* Creates a new element.
*
* @return DOMElement The created element.
*/
protected function _createElement()
{
$class = $this->getClassForElement($this->_reader->localName);
return new $class(
$this->_reader->name,
$this->_reader->value,
$this->_reader->namespaceURI
);
}
/**
* Adds the current attributes to an $element.
*
* @param DOMElement $element
* @return void
*/
protected function _addAttributes(DOMElement $element)
{
while ($this->_reader->moveToNextAttribute()) {
$this->_reader->prefix && ($uri = $this->_reader->lookupNamespace($this->_reader->prefix))
? $element->setAttributeNS($uri, $this->_reader->name, $this->_reader->value)
: $element->setAttribute($this->_reader->name, $this->_reader->value);
}
}
}
/**
*具有基于元素的类注册表的专用Xml解析器。
*
*此类使用XMLReader扩展进行文档解析并创建
*一个DOM树,每个元素类型都有单独的DomeElement子类。
*
*@author Andreas Traber
*
*@自2012年4月21日起
*@package-XML
*/
类高级语法分析器
{
/**
*映射已注册的类。
*@var数组
*/
受保护的$_elementClasses=array();
/**
*未知元素的默认类。
*@var字符串
*/
受保护的$\u defaultElementClass='DomeElement';
/**
*用于Xml解析的读取器。
*@var XMLReader
*/
受保护的$u读取器;
/**
*文档对象。
*@var DOMDocument
*/
受保护的$u文件;
/**
*狗
fido: foo
scratchy: foo
----
lucky: bar
====
<?xml version="1.0"?>
<animals>
<Foo name="fido"><action>something</action></Foo>
<Bar name="lucky" done="1"/>
<Foo name="scratchy"><action>something</action></Foo>
<Ham name="flicka"/>
<Egg name="donald"/>
</animals>
/**
* Specialized Xml parser with element based class registry.
*
* This class uses the XMLReader extension for document parsing and creates
* a DOM tree with individual DOMElement subclasses for each element type.
*
* @author Andreas Traber < a.traber (at) rivo-systems (dot) com >
*
* @since April 21, 2012
* @package XML
*/
class AdvancedParser
{
/**
* Map with registered classes.
* @var array
*/
protected $_elementClasses = array();
/**
* Default class for unknown elements.
* @var string
*/
protected $_defaultElementClass = 'DOMElement';
/**
* The reader for Xml parsing.
* @var XMLReader
*/
protected $_reader;
/**
* The document object.
* @var DOMDocument
*/
protected $_document;
/**
* The current parsing element.
* @var DOMElement
*/
protected $_currentElement;
/**
* Gets the fallback class for unknown elements.
*
* @return string
*/
public function getDefaultElementClass()
{
return $this->_defaultElementClass;
}
/**
* Sets the fallback class for unknown elements.
*
* @param string $class
* @return void
* @throws Exception $class is not a subclass of DOMElement.
*/
public function setDefaultElementClass($class)
{
switch (true) {
case $class === null:
$this->_defaultElementClass = 'DOMElement';
break;
case !$class instanceof DOMElement:
throw new Exception($class.' must be a subclass of DOMElement');
default:
$this->_defaultElementClass = $class;
}
}
/**
* Registers the class for a specified element name.
*
* @param string $elementName.
* @param string $class.
* @return void
* @throws Exception $class is not a subclass of DOMElement.
*/
public function registerElementClass($elementName, $class)
{
switch (true) {
case $class === null:
unset($this->_elementClasses[$elementName]);
break;
case !$class instanceof DOMElement:
throw new Exception($class.' must be a subclass of DOMElement');
default:
$this->_elementClasses[$elementName] = $class;
}
}
/**
* Gets the class for a given element name.
*
* @param string $elementName
* @return string
*/
public function getClassForElement($elementName)
{
return $this->_elementClasses[$elementName]
? $this->_elementClasses[$elementName]
: $this->_defaultElementClass;
}
/**
* Parse Xml Data from string.
*
* @see XMLReader::XML()
*
* @param string $source String containing the XML to be parsed.
* @param string $encoding The document encoding or NULL.
* @param string $options A bitmask of the LIBXML_* constants.
* @return DOMDocument The created DOM tree.
*/
public function parseString($source, $encoding = null, $options = 0)
{
$this->_reader = new XMLReader();
$this->_reader->XML($source, $encoding, $options);
return $this->_parse();
}
/**
* Parse Xml Data from file.
*
* @see XMLReader::open()
*
* @param string $uri URI pointing to the document.
* @param string $encoding The document encoding or NULL.
* @param string $options A bitmask of the LIBXML_* constants.
* @return DOMDocument The created DOM tree.
*/
public function parseFile($uri, $encoding = null, $options = 0)
{
$this->_reader = new XMLReader();
$this->_reader->open($uri, $encoding, $options);
return $this->_parse();
}
/**
* The parser.
*
* @return DOMDocument The created DOM tree.
*/
protected function _parse()
{
$this->_document = new DOMDocument('1.0', 'utf-8');
$this->_document->_elements = array(); // keep references to elements
$this->_currentElement = $this->_document;
while ($this->_reader->read()) {
switch ($this->_reader->nodeType) {
case XMLReader::ELEMENT:
$this->_reader->isEmptyElement
? $this->_addElement()
: $this->_currentElement = $this->_addElement();
break;
case XMLReader::END_ELEMENT:
$this->_currentElement = $this->_currentElement->parentNode;
break;
case XMLReader::CDATA:
$this->_currentElement->appendChild(
$this->_document->createCDATASection($this->_reader->value)
);
break;
case XMLReader::TEXT:
case XMLReader::SIGNIFICANT_WHITESPACE:
$this->_currentElement->appendChild(
$this->_document->createTextNode($this->_reader->value)
);
break;
case XMLReader::COMMENT:
$this->_currentElement->appendChild(
$this->_document->createComment($this->_reader->value)
);
break;
}
}
$this->_reader->close();
return $this->_document;
}
/**
* Adds the current element into the DOM tree.
*
* @return DOMElement The added element.
*/
protected function _addElement()
{
$element = $this->_createElement();
// It's important to keep a reference to each element.
// Elements without any reference were destroyed by the
// garbage collection and loses their type.
$this->_document->_elements[] = $element;
$this->_currentElement->appendChild($element);
$this->_addAttributes($element);
return $element;
}
/**
* Creates a new element.
*
* @return DOMElement The created element.
*/
protected function _createElement()
{
$class = $this->getClassForElement($this->_reader->localName);
return new $class(
$this->_reader->name,
$this->_reader->value,
$this->_reader->namespaceURI
);
}
/**
* Adds the current attributes to an $element.
*
* @param DOMElement $element
* @return void
*/
protected function _addAttributes(DOMElement $element)
{
while ($this->_reader->moveToNextAttribute()) {
$this->_reader->prefix && ($uri = $this->_reader->lookupNamespace($this->_reader->prefix))
? $element->setAttributeNS($uri, $this->_reader->name, $this->_reader->value)
: $element->setAttribute($this->_reader->name, $this->_reader->value);
}
}
}