代码之家  ›  专栏  ›  技术社区  ›  tex

用RegisterNodeClass在PHP中扩展domElement

  •  2
  • tex  · 技术社区  · 16 年前

    registerNodeClass 很适合在PHP中扩展各种基于domnode的dom类,但我需要更深入一层。

    我创造了一个 extDOMElement 延伸 DOMElement . 这个很好用 寄存器代码类 ,但我想要一些更像这样的东西: registerNodeClass("DOMElement->nodeName='XYZ'", 'extDOMXYZElement')

    请考虑以下XML文档:animals.xml:

    <animals>
        <dog name="fido" />
        <dog name="lucky" />
        <cat name="scratchy" />
        <horse name="flicka" />
    </animals>
    

    考虑以下代码:

    extDomDocument extends DOMDocument {
        public function processAnimals() {
            $animals = $this->documentElement->childNodes;
            foreach($animals as $animal) {
                $animal->process();
            }
        }
    }
    
    
    extDOMElement extends DOMElement {
        public function process() {
            if ($this->nodeName=='dog'){
                $this->bark();
            } elseif ($this->nodeName=='cat'){
                $this->meow();
            } elseif  ($this->nodeName=='horse'){
                $this->whinny();
            }
            this->setAttribute('processed','true');
        }
        private function bark () {
            echo "$this->getAttribute('name') the $this->nodeName barks!";
        }
        private function meow() {
            echo "$this->getAttribute('name') the $this->nodeName meows!";
        }
        private function whinny() {
            echo "$this->getAttribute('name') the $this->nodeName whinnies!";
        }
    }
    
    $doc = new extDOMDocument();
    $doc->registerNodeClass('DOMElement', 'extDOMElement');
    $doc->loadXMLFile('animals.xml');
    $doc->processAnimals();
    $doc->saveXMLFile('animals_processed_' . Now() . '.xml');
    

    输出: 狗叫起来了! 幸运的狗吠叫! 抓挠猫叫! 轻拂马的呜呜声!

    我不想把bark()、meow()和whinny()放在extdomElement中-我想把它们分别放在extdomDogElement、extdomCateElement和extdomHorseElement中。

    我看过这里的装饰和策略模式,但我不确定如何继续。当前设置工作正常,但我希望在 extdomElement 为每个元素名使用单独的类,这样我就可以将特定于每个元素的方法和属性从主类中分离出来。

    3 回复  |  直到 13 年前
        1
  •  0
  •   Community Mohan Dere    9 年前

    我不能确定,但你要做的是 smell ,像 Gordon 已经指出了。
    不管怎样…你 能够 使用 __call() 根据实际节点(键入/contents/…)在extdomElement对象上公开不同的方法。为此,extdomElement对象可以存储一个根据元素的“类型”实例化的助手对象,然后将方法调用委托给该助手对象。就我个人而言,我不太喜欢它,因为它并不能使文档、测试和调试变得更容易。如果你觉得可行,我可以写一个独立的例子。


    这当然需要评论/文档…因为我现在没有时间,所以正在进行工作…

    <?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');
      }
    }
    

    印刷品

    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>
    
        2
  •  1
  •   Andreas Traber    14 年前

    我也有同样的问题。我的解决方案是基于xmlreader扩展编写自己的解析器。生成的advancedParser类对我来说非常有效。可以为每个元素名称注册单独的元素类。通过扩展advancedParser类并重写getClassRelement()方法,还可以根据元素名动态计算所需类的名称。

    /**
     * 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);
            }
        }
    }
    
        3
  •  0
  •   Gordon Haim Evgi    16 年前

    编辑 对于您所显示的代码,完全不扩展domElement会更容易吗?只需将常规的domElements传递给您的处理策略,例如

    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);
    

    问题更新前的原始答案

    不确定这是否是您正在寻找的,但是如果您想要专门的domElements,您可以简单地创建它们并直接使用它们,例如绕过 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();
    

    我觉得你用起来不方便 寄存器代码类 根据标记名或对解析的影响来实例化元素。但您可以覆盖domdocument的 创建元素 类,例如

    class MyDOMDocument extends DOMDocument
    {
        public function createElement($nodeType, $value = NULL)
        {
            $nodeType = $nodeType . 'Element';
            return new $nodeType($value);
        }
    }
    $dom = new MyDOMDocument;
    $dom->loadXML('<animals/>');
    var_dump( $dom->createElement('Dog', 'Sparky') ); // DogElement
    var_dump( $dom->createElement('Cat', 'Tinky') ); // CatElement
    echo $dom->saveXml();
    
    推荐文章