Kji has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/189494

Change subject: Implemented hierarchySelected parser function, upgraded 
hierarchySubtree, bugfix for hierarchyParent.
......................................................................

Implemented hierarchySelected parser function, upgraded hierarchySubtree,
bugfix for hierarchyParent.

Implemented pruned version of hierarchySelected parser function.

Change-Id: I5d7f394c1147683574bfe28ee7ee8ba034ee81d7

Fixed a syntax error.

Change-Id: I4472500550f1ae7e561c673e0189ffdb1a637284

Added documentation for MST functions.

Change-Id: If0f9ee10bbc7bb28c10f3e1ec7054d5a9f25a2d0

Added basics of new renderer for hierarchySelected.

Change-Id: Iad7c5f8a1110b88f4e6a51f0f71e58d47dc172e2

Render selected now correctly marks selected items with checked boxes.

Change-Id: I547a407460e6bbbf014a81df55f45953d044a037

Implemented collapsed displaymode for hierarchySelected parser function.

Change-Id: I8225cd65763c3a64483086f82562ec52e304b853

Fixed empty parent bug in hierarchyParent parser function.

Change-Id: I246bd9ef4ba0a6ee744d3d611673f72e4bf92291

Implemented showroot and collapsed display modes for hierarchySubtree parser 
function.

Change-Id: I26b4abf53b29f7f7b2afd00d172a4981a8a49435

Removed logging function.

Change-Id: I1ddf57997aad1923cdc9801f4dc455aab09f55d9

Modified hierarchySelected parser function so pruned displaymode is default 
since that is the only supported display mode currently.

Change-Id: I964cc59534a29b60506df07a0a70c63a70d6ad31

Updated hierarchySelected so that it will allow the selected pages list to be 
provided in link format.

Change-Id: I47cbee05fc6eaac42285531475baad87dd892e41
---
M HierarchyBuilder.class.php
M HierarchyBuilder.i18n.magic.php
M HierarchyBuilder.php
A HierarchyTree.php
A TreeNode.php
A renderHierarchySelected.js
6 files changed, 712 insertions(+), 11 deletions(-)


  git pull 
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/HierarchyBuilder 
refs/changes/94/189494/1

diff --git a/HierarchyBuilder.class.php b/HierarchyBuilder.class.php
index 371900c..b84cd57 100644
--- a/HierarchyBuilder.class.php
+++ b/HierarchyBuilder.class.php
@@ -39,6 +39,9 @@
        const LINK = 'link';
        const FORMAT = 'format';
        const DISPLAYNAMEPROPERTY = 'displaynameproperty';
+       const DISPLAYMODE = 'displaymode';
+       const SHOWROOT = 'showroot';
+       const COLLAPSED = 'collapsed';
 
        /**
         * This function gives the section number for a target page within a
@@ -228,8 +231,9 @@
                                // Note that if there is no hierarchical 
parent, then the parent
                                // will be empty.
                                $parent = self::getParent( $hierarchyRows, 
$row, $i );
-                               array_push( $parents, $parent);
-                               //return $parent;
+                               if ( $parent != '' ) {
+                                       array_push( $parents, $parent);
+                               }
                        }
                }
 
@@ -345,7 +349,7 @@
         *
         * @return string: The first page name found within $hierarchyRow.
         */
-       private function getPageNameFromHierarchyRow( $hierarchyRow ) {
+       public function getPageNameFromHierarchyRow( $hierarchyRow ) {
                $numMatches = preg_match_all( self::PAGENAMEPATTERN, 
$hierarchyRow, $matches );
                // give me the first subpattern match to be the name of the 
previous page
                $pageName = ( $numMatches > 0 ? $matches[1][0] : '' );
@@ -363,7 +367,7 @@
         *
         * @return number: The depth of $hierarchyRow.
         */
-       private function getDepthOfHierarchyRow( $hierarchyRow ) {
+       public function getDepthOfHierarchyRow( $hierarchyRow ) {
                $numMatches = preg_match_all( self::DEPTHPATTERN, 
$hierarchyRow, $matches );
                $depth = ( $numMatches > 0 ? strlen( $matches[1][0] ) : 0 );
                return $depth;
@@ -525,6 +529,84 @@
                $script = <<<END
 mw.loader.using(['ext.HierarchyBuilder.render'], function () {
        renderHierarchy("$hierarchyName", "$hierarchy", $collapsed, $numbered);
+});
+END;
+
+               global $wgOut;
+               $script = Html::inlineScript( $script );
+               $wgOut->addScript( $script );
+
+               return Html::element( 'div', array( 'id' => $hierarchyName ) );
+       }
+
+       public function renderHierarchySelected( $input, $attributes, $parser, 
$frame ) {
+               $hierarchyName = 'HierarchyDiv' . self::$m_hierarchy_num;
+               self::$m_hierarchy_num++;
+
+               if ( isset( $attributes['collapsed'] ) ) {
+                       $collapsed = htmlspecialchars( $attributes['collapsed'] 
);
+                       if ( $collapsed === 'collapsed' ) {
+                               $collapsed = 'true';
+                       }
+               } else  {
+                       $collapsed = 'false';
+               }
+
+               if ( isset( $attributes['displaynameproperty'] ) ) {
+                       $displayNameProperty =
+                               htmlspecialchars( 
$attributes['displaynameproperty'] );
+               } else  {
+                       $displayNameProperty = '';
+               }
+
+               if ( isset( $attributes['numbered'] ) ) {
+                       $numbered = htmlspecialchars( $attributes['numbered'] );
+                       if ( $numbered === 'numbered' ) {
+                               $numbered = 'true';
+                       }
+               } else {
+                       $numbered = 'false';
+               }
+
+               if ( isset( $attributes['selected'] ) ) {
+                       $selectedPages =
+                               json_encode( explode( ',', urldecode( 
$attributes['selected'] ) ) );
+               } else  {
+                       $selectedPages = '';
+               }
+
+               // this looks like it gets the property but it eats all the 
links.
+               $input = $parser->recursiveTagParse( $input, $frame );
+               $input = self::anchorLinkHolders( $input );
+               $input = $parser->replaceLinkHoldersText( $input );
+               $input = $parser->parse( $input,
+                       $parser->getTitle(),
+                       $parser->Options(),
+                       true,
+                       false )->getText();
+
+               $hierarchy = HierarchyBuilder::parseHierarchy( $input,
+                       $displayNameProperty, $dummy,
+                       function ( $pageName, $displayNameProperty, $data ) {
+                               $pageLinkArray = array();
+                               $title = Title::newFromText( $pageName );
+                               if ( $title ) {
+                                       $pageLinkArray['href'] = 
$title->getLinkURL();
+                               }
+                               if ( strlen( $displayNameProperty ) > 0 ) {
+                                       $pageName = 
HierarchyBuilder::getPageDisplayName( $pageName,
+                                               $displayNameProperty );
+                               }
+                               return Html::element( 'a', $pageLinkArray, 
$pageName );
+                       } );
+
+               $parser->getOutput()->addModules( 
'ext.HierarchyBuilder.renderSelected' );
+
+               $hierarchy = strtr( $hierarchy, array( '"' => "'" ) );
+
+               $script = <<<END
+mw.loader.using(['ext.HierarchyBuilder.renderSelected'], function () {
+       renderHierarchySelected("$hierarchyName", "$hierarchy", $collapsed, 
$numbered, $selectedPages);
 });
 END;
 
@@ -771,7 +853,7 @@
         * @return array: An array with the root as the first element and each
         *  child subhierarchy as a subsequent element. 
         */
-       private static function splitHierarchy( $wikiTextHierarchy, $depth ) {
+       public static function splitHierarchy( $wikiTextHierarchy, $depth ) {
                $nextDepth = "\n" . $depth . "*";
                $r1 = "/\*/"; // this guy finds * characters
                // this is building the regex that will be used later
@@ -846,7 +928,11 @@
                                $subHierarchyRows[0] = str_repeat( '*', strlen( 
$depth ) + 1) . $subHierarchyRows[0]; // put the stars on the root row to start
                                $result = array_reduce( $subHierarchyRows, 
                                        function( $carry, $item ) use ( $depth 
) {
-                                               $carry .= "\n" . substr( $item, 
strlen($depth));
+                                               if ( $carry != '' ){
+                                                       $carry .= "\n" . 
substr( $item, strlen($depth));
+                                               } else {
+                                                       $carry = substr( $item, 
strlen($depth));
+                                               }
                                                return $carry;
                                        }
                                );
@@ -861,4 +947,11 @@
 
                return '';
        }
+
+       public static function parseHierarchyToTree( $hierarchyPageName, 
$hierarchyPropertyName ) {
+               $hierarchy = HierarchyBuilder::getPropertyFromPage( 
$hierarchyPageName, $hierarchyPropertyName );
+               $hierarchy = "[[Hierarchy_Root]]\n" . $hierarchy;
+
+               HierarchyTree::parseHierarchyToTree( $hierarchy );
+       }
 }
diff --git a/HierarchyBuilder.i18n.magic.php b/HierarchyBuilder.i18n.magic.php
index bb927b7..fb95d37 100644
--- a/HierarchyBuilder.i18n.magic.php
+++ b/HierarchyBuilder.i18n.magic.php
@@ -29,5 +29,6 @@
        'hierarchySectionNumber' => array(0, 'hierarchySectionNumber'),
        'hierarchyParent' => array(0, 'hierarchyParent'),
        'hierarchyChildren' => array(0, 'hierarchyChildren'),
-       'hierarchySubtree' => array(0, 'hierarchySubtree')
+       'hierarchySubtree' => array(0, 'hierarchySubtree'),
+       'hierarchySelected' => array(0, 'hierarchySelected')
 );
diff --git a/HierarchyBuilder.php b/HierarchyBuilder.php
index c81d0fd..60a6a70 100644
--- a/HierarchyBuilder.php
+++ b/HierarchyBuilder.php
@@ -61,6 +61,8 @@
 $wgAutoloadClasses['HierarchyBuilder'] = __DIR__ . 
'/HierarchyBuilder.class.php';
 $wgAutoloadClasses['HierarchyFormInput'] = __DIR__ . '/HierarchyFormInput.php';
 $wgAutoloadClasses['HierarchySelectFormInput'] = __DIR__ . 
'/HierarchySelectFormInput.php';
+$wgAutoloadClasses['HierarchyTree'] = __DIR__ . '/HierarchyTree.php';
+$wgAutoloadClasses['TreeNode'] = __DIR__ . '/TreeNode.php';
 
 $wgMessagesDirs['HierarchyBuilder'] = __DIR__ . "/i18n";
 
@@ -78,6 +80,15 @@
        'localBasePath' => __DIR__,
        'remoteExtPath' => 'HierarchyBuilder',
        'scripts' => 'renderHierarchy.js',
+       'dependencies' => array(
+               'ext.HierarchyBuilder.jstree'
+       )
+);
+
+$wgResourceModules['ext.HierarchyBuilder.renderSelected'] = array(
+       'localBasePath' => __DIR__,
+       'remoteExtPath' => 'HierarchyBuilder',
+       'scripts' => 'renderHierarchySelected.js',
        'dependencies' => array(
                'ext.HierarchyBuilder.jstree'
        )
@@ -115,12 +126,101 @@
        $parser->setFunctionHook( 'hierarchyParent', 'parent' );
        $parser->setFunctionHook( 'hierarchyChildren', 'children' );
        $parser->setFunctionHook( 'hierarchySubtree', 'subhierarchy' );
+       $parser->setFunctionHook( 'hierarchySelected', 'hierarchySelected' );
 
        $parser->setHook( 'hierarchy', 'renderHierarchy' );
+       $parser->setHook( 'hierarchySelected', 'renderHierarchySelected');
        global $sfgFormPrinter;
        $sfgFormPrinter->registerInputType( 'HierarchyFormInput' );
        $sfgFormPrinter->registerInputType( 'HierarchySelectFormInput' );
        return true;
+}
+
+/**
+ * This parser function will return only specific selected rows of a hierarchy
+ * in addition to any necessary contextual rows. 
+ *
+ * The returned hierarchy is displayd similarly to the 
HierarchySelectFormInput,
+ * with each row preceeded by a checkbox. However, the checkboxes will be 
inactive.
+ *
+ * For a given set of selected rows, only those rows will be provided from the
+ * hierarchy in addition to the minimal necessary contextual rows needed to 
display
+ * the hierarchical relationships. For example, if a single selected row is 
given,
+ * but that row is a leaf node which is 5 levels deep within the hierarchy, 
then 
+ * that row will be given along with each of its ancestors. This is conidered 
the
+ * "pruned" behavior.
+ *
+ * The "collapsed" behavior will not remove any branches of the hierarchy, even
+ * when those branches do not contain any of the specified selected rows. 
Instead,
+ * these unnecessary branches will be collapsed initially, allowing only the
+ * selected rows and their siblings to be shown.
+ *
+ * @param $parser: Parser
+ * @return I don't know yet.
+ * 
+ * Example invokation:
+ * @code
+ * {{#hierarchySelected:<list of page names>|<hierarchy page name>|<hierarchy 
property>}} 
+ * {{#hierarchySelected:<list of page names>|<hierarchy page name>|<hierarchy 
property>|pruned}}
+ * {{#hierarchySelected:<list of page names>|<hierarchy page name>|<hierarchy 
property>|collapsed}}
+ * @endcode
+ */
+function hierarchySelected( $parser ) {
+       $params = func_get_args();
+       if ( count( $params ) < 4 || count( $params ) > 5) {
+               $output = '';
+       } else {
+               $selectedPages = $params[1];
+               $hierarchyPageName = $params[2];
+               $hierarchyPropertyName = $params[3];
+               // if "pruned" is given, then set the displaymode to pruned. 
otherwise, "collapsed"
+               if ( isset( $params[4] ) && $params[4] == 'collapsed') {
+                       $displayMode = 'collapsed';
+               } else {
+                       $displayMode = 'pruned';
+               }
+
+               $wikitextHierarchy = HierarchyBuilder::getPropertyFromPage( 
$hierarchyPageName, $hierarchyPropertyName );
+               // this is where we ask HierarchyBuilder class to actually do 
the work for us.
+               $hierarchyTree = HierarchyTree::fromWikitext( 
$wikitextHierarchy );
+
+               $normalizedSelectedPages = 
+                       array_map(
+                               function( $page ) {
+                                       $pagename = 
HierarchyBuilder::getPageNameFromHierarchyRow( $page );
+                                       if ( $pagename == '' ) {
+                                               $pagename = $page;
+                                       }
+                                       return $pagename;
+                               },
+                               explode( ',', $selectedPages )
+                       );
+               $mst = $hierarchyTree->getMST( $normalizedSelectedPages );
+               
+               // output formatting
+               $flatNormalizedSelectedPages =                  
+                       array_reduce( $normalizedSelectedPages, 
+                                       function( $carry, $item ) {
+                                               if ( $carry == '') {
+                                                       $carry = $item;
+                                               } else {
+                                                       $carry .= ',' . $item;
+                                               }
+                                               return $carry;
+                                       }
+                               );
+               $selected = htmlspecialchars( str_replace( " ", "%20", 
$flatNormalizedSelectedPages     ) );
+
+               $output = '';
+               if ( $displayMode == 'collapsed') {
+                       $output = "<hierarchySelected collapsed 
selected=$selected>" . (string)$mst . '</hierarchySelected>';
+               } else {
+                       $output = "<hierarchySelected selected=$selected>" . 
(string)$mst . '</hierarchySelected>';
+               }
+               $output = $parser->recursiveTagParse( $output );
+
+       }
+       return $parser->insertStripItem( $output, $parser->mStripState );
 }
 
 /**
@@ -171,6 +271,18 @@
                if ( isset( 
$optionalParams[HierarchyBuilder::DISPLAYNAMEPROPERTY] ) ) {
                        $displaynameproperty = 
$optionalParams[HierarchyBuilder::DISPLAYNAMEPROPERTY];
                }
+               /*$displaymode = '';
+               if ( isset( $optionalParams[HierarchyBuilder::DISPLAYMODE] ) ) {
+                       $displaymode = 
$optionalParams[HierarchyBuilder::DISPLAYMODE];
+               }*/
+               $showroot = '';
+               if ( isset( $optionalParams[HierarchyBuilder::SHOWROOT] ) ) {
+                       $showroot = $optionalParams[HierarchyBuilder::SHOWROOT];
+               }
+               $collapsed = '';
+               if ( isset( $optionalParams[HierarchyBuilder::COLLAPSED] ) ) {
+                       $collapsed = 
$optionalParams[HierarchyBuilder::COLLAPSED];
+               }
 
                $output = HierarchyBuilder::getSubhierarchy(
                        $rootNode,
@@ -178,12 +290,32 @@
                        $hierarchyPropertyName
                );
 
-               // this is the default output display mode
+               // this is where we have to handle the default mode which is 
not showroot and not collapsed
+               if ( $showroot == '' ) {
+                       // fix $output so only the children are given
+                       $hierarchyrows = preg_split( '/\n/', $output );
+                       $root = $hierarchyrows[0];
+                       $children = array_slice( $hierarchyrows, 1 );
+
+                       $depth = HierarchyBuilder::getDepthOfHierarchyRow( 
$root );
+                       $output = array_reduce( $children, 
+                                       function( $carry, $item ) use ( $depth 
) {
+                                               if ($carry != '') {
+                                                       $carry .= "\n" . 
substr( $item, strlen($depth));
+                                               } else {
+                                                       $carry = substr( $item, 
strlen($depth));
+                                               }
+                                               return $carry;
+                                       }
+                               );
+               }
+
+               // this is the default output display format
                if ($format != 'ul') {
                        if ( $displaynameproperty == '' ) {
-                               $output = "<hierarchy>$output</hierarchy>";
+                                       $output = "<hierarchy 
$collapsed>$output</hierarchy>";
                        } else {
-                               $output = "<hierarchy 
displaynameproperty=$displaynameproperty>$output</hierarchy>";
+                               $output = "<hierarchy $collapsed 
displaynameproperty=$displaynameproperty>$output</hierarchy>";
                        }
                }
                // otherwise it's the bulleted format and we don't modify 
output.
@@ -527,6 +659,15 @@
                'noparse' => false );
 }
 
+function renderHierarchySelected( $input, $attributes, $parser, $frame ) {
+       $hierarchyBuilder = new HierarchyBuilder;
+       $output = $hierarchyBuilder->renderHierarchySelected( $input, 
$attributes, $parser,
+               $frame );
+       $parser->disableCache();
+       return array( $parser->insertStripItem( $output, $parser->mStripState ),
+               'noparse' => false );
+}
+
 /**
  * Helper function for parsing a list of named parser function parameters.
  *
@@ -554,6 +695,8 @@
        $ret = preg_split( '/=/', $param, 2 );
        if ( count( $ret ) > 1 ) {
                $paramArray[$ret[0]] = $ret[1];
+       } else {
+               $paramArray[$ret[0]] = $ret[0];
        }
        return $paramArray;
-}
+}
\ No newline at end of file
diff --git a/HierarchyTree.php b/HierarchyTree.php
new file mode 100644
index 0000000..4cbaddc
--- /dev/null
+++ b/HierarchyTree.php
@@ -0,0 +1,149 @@
+<?php
+ 
+/*
+ * Copyright (c) 2014 The MITRE Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+class HierarchyTree {
+       protected $root = null;
+
+       function __construct() {
+               //$root = $node;
+       }
+
+       public static function fromNode( $node ) {
+               $instance = new self();
+               $instance->loadRootNode( $node );
+               return $instance;
+       }
+
+       public static function fromWikitext( $wikitextHierarchy ) {
+               $hierarchy = "[[Hierarchy_Root]]\n" . $wikitextHierarchy;
+               $rootNode = HierarchyTree::parseHierarchyToTreeHelper( 
$hierarchy, '' );
+
+               $instance = new self();
+               $instance->loadRootNode( $rootNode );
+               return $instance;
+       }
+
+       private function loadRootNode( $node ) {
+               $this->root = $node;
+       }
+
+       private function parseHierarchyToTreeHelper( $wikiTextHierarchy, $depth 
) {
+               $curRootAndChildren = 
HierarchyBuilder::splitHierarchy($wikiTextHierarchy, $depth);
+               $curRootText = $curRootAndChildren[0];
+               $curChildrenText = array_slice($curRootAndChildren, 1);
+
+               $curRootNode = new TreeNode( $depth . $curRootText );
+
+               foreach ( $curChildrenText as $childText ) {
+                       $childNode = self::parseHierarchyToTreeHelper( 
$childText, $depth . '*' );
+                       $curRootNode->getValue();
+                       $curRootNode->addChild( $childNode );
+               }
+
+               return $curRootNode;
+       }
+
+       public function __toString() {
+               return $this->serialize( $this->root );
+       }
+
+       private function serialize( $node ) {
+               $returnValue = '';
+               if ( $node != null) {
+                       if ( $node != $this->root) {
+                               $returnValue .= $node->getValue() . "\n";
+                       }
+                       if ( count( $node->getChildren() ) ) {
+                               foreach ( $node->getChildren() as $child ) {
+                                       $returnValue .= 
$this->serialize($child);
+                               }
+                       }
+               }
+
+               return $returnValue;
+       }
+
+       /**
+        * Returns the MST that is a subtree of this hierarchy containing the
+        * specified rows and the hierarchy root.
+        *
+        * The returned MST will always contain this hierarchy's root node, 
each of
+        * the specified target rows, and any intermediate nodes from the 
hierarchy
+        * which are necessary to form a connected graph.
+        *
+        * @param array $rows: List of targeted rows within the hierarchy that 
must
+        *  included in the MST.
+        *
+        * @return HierarchyTree: A new HierarchyTree object representing the 
MST
+        *  that holds all the targeted rows in $rows
+        */
+       public function getMST( array $rows ) {
+               $mstRoot = $this->getMSTHelper( $this->root, $rows );
+               $mst = self::fromNode( $mstRoot );
+               return $mst;
+       }
+
+       /**
+        * Helper function to return a branch or subbranch of the final MST.
+        *
+        * Logically this function proceeds as follows. If the current node 
being
+        * examined is one of the targeted rows, then a copy of this node will 
be 
+        * returned (children may or may not be included). If any descendants of
+        * the current node are targeted rows, then the immediate child of the
+        * current node leading to that targeted descendant is included as a 
child
+        * of the returned node. This proceeds recursively until the entire tree
+        * has been explored.
+        *
+        * @param TreeNode $node: The current node being examined within this 
hierarchy.
+        * @param array $rows: The list of target rows to be included in the 
MST.
+        *
+        * @return TreeNode: A treenode that forms the root of a branch of the 
final
+        *  MST. If the current node being examined in the hierarchy is one of 
the
+        *  targeted rows or any of it's children is a targeted row, then a 
TreeNode
+        *  object will be returned here. Otherwise, the entire subtree of this
+        *  hierarchy that is rooted at the current node is not a part of the 
final
+        *  MST and null is returned instead.
+        */
+       private function getMSTHelper( TreeNode $node, array $rows ) {
+               // make a copy of this node
+               $clone = new TreeNode( $node->getValue() );
+
+               // if any of the children are found in $rows then add them to 
the copy
+               if ( count( $node->getChildren()) > 0 ) {
+                       foreach ( $node->getChildren() as $child ) {
+                               $subtree = $this->getMSTHelper( $child, $rows );
+                               if ( $subtree != null ) {
+                                       $clone->addChild( $subtree );
+                               }
+                       }
+               }
+
+               // if children are in $rows or self is in rows then return the 
copy
+               if ( $clone->getChildren() != null || in_array( 
HierarchyBuilder::getPageNameFromHierarchyRow( $node->getValue() ), $rows ) ) {
+                       return $clone;
+               } else { // otherwise this whole branch gets cut
+                       return null;
+               }
+       }
+}
\ No newline at end of file
diff --git a/TreeNode.php b/TreeNode.php
new file mode 100644
index 0000000..35976f7
--- /dev/null
+++ b/TreeNode.php
@@ -0,0 +1,61 @@
+<?php
+ 
+/*
+ * Copyright (c) 2014 The MITRE Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+class TreeNode {
+       protected $value = null; // this is the actuall row value like 
"***[[Hierarchy Builder]]"
+       protected $parent = null; // this is a pointer to the parent node 
within the tree
+       protected $children = null; // an array of pointers to children
+
+       function __construct( $text ) {
+       $this->value = $text;
+       }
+
+       public function getValue() {
+               return $this->value;
+       }
+
+       public function getParent() {
+               return $this->parent;
+       }
+
+       public function getChildren() {
+               return $this->children;
+       }
+
+       public function setValue( $text ) {
+               $this->value = $text;
+       }
+
+       public function setParent( TreeNode $node ) {
+               $this->parent = $node;
+       }
+
+       public function addChild( TreeNode $node ) {
+               if ( $this->children == null ) {
+                       $this->children = array( $node );
+               } else {
+                       array_push( $this->children, $node );   
+               }
+       }
+}
\ No newline at end of file
diff --git a/renderHierarchySelected.js b/renderHierarchySelected.js
new file mode 100644
index 0000000..f8915f7
--- /dev/null
+++ b/renderHierarchySelected.js
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2014 The MITRE Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+( function( $ ) {
+       /**
+        * Gobal function to display a hierarchy.
+        *
+        * @param {string} divId Name of the div to render the hierarchy into
+        * @param {string} hierarchy A 2 dimensional array of the data
+        * - for each row in hierarchy, the first column is the level of 
indentation,
+        *       the second column is the name of the page, and the third 
column is
+        *       the URL of the page
+        * @param {string} collapsed A Boolean that indicates if the tree should
+        *  start collapsed
+        * @param {string} numbered A boolean that indicates if the hierarchy
+        *  should be auto-numbered
+        */
+       window.renderHierarchySelected = function( divId, hierarchy, collapsed, 
numbered, selectedComponents ) {
+               /**
+                * @class RenderHierarchy
+                *
+                * Object literal implementing hierarchy rendering.
+                *
+                * This is actually an object literal which is defined and used 
by the
+                * global renderHierarchy function which contains all of the 
necessary
+                * functions for rendering a hierarchy.
+                */
+               ( {
+                       /**
+                        * Render a hierarchy on a page.
+                        *
+                        * @param {string} divId Name of the div to render the 
hierarchy into
+                        * @param {string} hierarchy A 2 dimensional array of 
the data
+                        * - for each row in hierarchy, the first column is the 
level of
+                        *   indentation, the second column is the name of the 
page, and the
+                        *   third column is the URL of the page
+                        * @param {string} collapsed A Boolean that indicates 
if the tree
+                        *  should start collapsed
+                        * @param {string} numbered A boolean that indicates if 
the hierarchy
+                        *  should be auto-numbered
+                        */
+                       render: function( divId, hierarchy, collapsed, 
numbered, selectedComponents ) {
+                               if ( hierarchy.length < 1 ) {
+                                       return;
+                               }
+
+                               if ( numbered ) {
+                                       var $hierarchy = $( hierarchy );
+                                       $hierarchy = this.numberHtml( 
$hierarchy );
+                                       hierarchy = $hierarchy[ 0 ].outerHTML;
+                               }
+
+                               if ( selectedComponents && 
selectedComponents.length > 0) {
+                                       for (var i = 0; i < 
selectedComponents.length; i++) {
+                                               selectedComponents[i] = 
selectedComponents[i].replace("%20", " ");
+                                       }
+                               }
+                       
+
+                               var obj = this;
+
+                               var jqDivId = "#" + divId;
+                               $( jqDivId )
+                                       .html( hierarchy )
+                                       .attr('dir', 'ltr');
+                               $( jqDivId + " * li" )
+                                       .css( "list-style-image", "none" );
+                               $( jqDivId )
+                                       .bind( "loaded.jstree", function( 
event, data ) {
+                                               obj.initializeTree( jqDivId, 
selectedComponents, true, collapsed );                                     
+                                       } );
+                               $( jqDivId )
+                                       .bind( "refresh.jstree", function( 
event, data ) {
+                                               obj.initializeTree( jqDivId, 
selectedComponents, true, collapsed );
+                                       } );
+
+                               $( jqDivId )
+                                       .jstree( {
+                                               "themes": {
+                                                       "theme": "apple",
+                                                       "dots": true,
+                                                       "icons": false
+                                               },
+                                               "checkbox": {
+                                                       "two_state": true
+                                               },
+                                               "types": {
+                                                       "types": {
+                                                               "disabled": {
+                                                                       
"check_node": false,
+                                                                       
"uncheck_node": false
+                                                               }
+                                                       }
+                                               },
+                                               "plugins": [ "themes", 
"html_data", "checkbox", "types" ]
+                                       } );
+                       },
+
+                       /**
+                        * Initializes the displayed hierarchy to display the 
properly and
+                        * have the correct behaviors.
+                        *
+                        * @param {string} jqDivId The id of the div containing 
the hierarchy
+                        *  to be initialized.
+                        * @param {Array} selectedComponents A list of the rows 
which should
+                        *  be displayed as selected to begin with.
+                        * @param {boolean} init A boolean to indicate whether 
the hierarchy
+                        *  is being initialized or refreshed.
+                        * @param {boolean} collapsed A boolean to indicate 
whether the
+                        *  hierarchy should start out in collapsed or expanded 
form.
+                        */
+                       initializeTree: function( jqDivId, selectedComponents, 
init, collapsed ) {
+                               var obj = this;
+
+                               if ( collapsed ) {
+                                       $( jqDivId )
+                                               .jstree( "close_all" );
+                               } else {
+                                       $( jqDivId )
+                                               .jstree( "open_all" );
+                               }
+
+                               $( jqDivId + "* li" )
+                                       .each( function() {
+                                               var parent = $( this );
+                                               $( this )
+                                                       .children( "a" )
+                                                       .each( function() {
+                                                               var $element = 
$( this );
+                                                               var elementName 
= $( this )
+                                                                       
//.children( "span:first" )
+                                                                       .text()
+                                                                       .trim();
+                                                               if ( 
obj.isSelectedHierarchyComponent( elementName,
+                                                                       
selectedComponents ) ) {
+                                                                       $( 
jqDivId )
+                                                                               
.jstree( "check_node", parent );
+                                                                       $( this 
).attr("class", "selectedHierarchyRow");
+                                                               }
+                                                       } );
+                                       } );
+
+                               $( jqDivId + "* li" )
+                                       .each( function() {
+                                               $.jstree._reference( jqDivId )
+                                                       .set_type( "disabled",
+                                                               $( this ) );
+                                       } );
+                       },
+
+                       /**
+                        * Determine if a row has been selected or not.
+                        *
+                        * @param {string} elementName The row who's selected 
status is being
+                        *  determined.
+                        * @param {Array} selectedComponents The list of 
currently seleccted
+                        *  hierarchy rows.
+                        *
+                        * @return {boolean} True if elementName is included in 
the array
+                        *  selectedComponents. False otherwise.
+                        */
+                       isSelectedHierarchyComponent: function( elementName,
+                               selectedComponents ) {
+                               if ( selectedComponents && 
selectedComponents.length > 0 ) {
+                                       var pageName = elementName;
+                                       var index = $.inArray( pageName, 
selectedComponents );
+                                       if ( index !== -1 ) {
+                                               return true;
+                                       }
+                               }
+                               return false;
+                       },
+
+                       /**
+                        * Updates a hierarchy to include section numbers.
+                        *
+                        * This function takes an HTML hierarchy and applies 
section numbers
+                        * to each row of the hierarchy. The resulting HTML 
hierarchy with
+                        * section numbers applied is then returned.
+                        *
+                        * @param {Object} uListRoot A jquery object 
representing the top
+                        *  level ul element of the entire HTML hierarchy.
+                        */
+                       numberHtml: function( uListRoot ) {
+                               var list = uListRoot.clone();
+                               return this.numberHtmlHelper( list, "" );
+                       },
+
+                       /**
+                        * Helper function for applying section numbers to a 
hierarchy.
+                        *
+                        * This is a helper function which recursively 
traverses a hierarchy
+                        * and computes and applies section numbers to the 
beginning of each
+                        * row in that hierarchy.
+                        *
+                        * @param {Object} uListRoot A jquery object 
representing the top
+                        *  level ul element of the entire HTML hierarchy.
+                        * @param {string} numberPrefix A string containing the 
immediate
+                        *  parent's complete section number. (ex: 1.1) This is 
used to
+                        *  construct the child's complete section number by 
using the
+                        *  numberPrefix as a suffix. (ex: 1.1 is used to 
create 1.1.1)
+                        *
+                        * @return {Object} A jquery object representing the 
top level ul
+                        *  element of the HTML hierarchy with section numbers 
applied.
+                        */
+                       numberHtmlHelper: function( uListRoot, numberPrefix ) {
+                               var that = this;
+
+                               var $numberSuffix = 1; // this is the 
subsection number for a particular child. It starts at one because the first 
child is numbered 1.
+
+                               var cur = uListRoot[ 0 ];
+                               $( cur )
+                                       .children( $( "li" ) )
+                                       .each( function() {
+                                               var $children = $( this )
+                                                       .contents();
+
+                                               var childNumber = numberPrefix 
=== "" ? $numberSuffix++ : numberPrefix + "." + $numberSuffix++;
+                                               var numberedChild = childNumber 
+ " " + $children.first()
+                                                       .text();
+                                               $children.first()
+                                                       .text( numberedChild );
+
+                                               var $sublist = 
$children.filter( "ul" );
+                                               if ( $sublist.size() > 0 ) {
+                                                       that.numberHtmlHelper( 
$sublist, childNumber ); // recurse on the sublist
+                                               }
+                                       } );
+                               return uListRoot;
+                       }
+               } )
+               .render( divId, hierarchy, collapsed, numbered, 
selectedComponents );
+       };
+}( jQuery ) );

-- 
To view, visit https://gerrit.wikimedia.org/r/189494
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I47cbee05fc6eaac42285531475baad87dd892e41
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/HierarchyBuilder
Gerrit-Branch: master
Gerrit-Owner: Kji <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to