Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
54 / 54
iaReMapKeys
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
10 / 10
32
100.00% covered (success)
100.00%
54 / 54
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
5 / 5
 mapHeaderRow
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
10 / 10
 getHeadersDuplicated
n/a
0 / 0
1
n/a
0 / 0
 getHeaderMap
n/a
0 / 0
1
n/a
0 / 0
 getHeaderMissing
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 getHeaderMissingWithoutDefaults
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 row2key
100.00% covered (success)
100.00%
1 / 1
8
100.00% covered (success)
100.00%
14 / 14
 isLastRowEmpty
n/a
0 / 0
1
n/a
0 / 0
 getRowKeysMissing
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getRowKeysEmpty
100.00% covered (success)
100.00%
1 / 1
5
100.00% covered (success)
100.00%
5 / 5
 prepareInternalData
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
7 / 7
 headerVariants
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 headerStandardize
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
3 / 3
<?php
namespace ia\Lib;
use ia\Util\Str;
use \URLify;
/**
 * iaReMapKeys
 *
 * Maps rowIn[$inKey] to rowsKeyed[$key] using header row to define the mapping.
 *
 * @notes
 *  default fills col if: not in read row, or it in read row ==='' || row === null
 *  rows and headers read are trimmed
 *
 * @version 1.0.4
 *
 * @example ../ia_examples/iaReMapKeys.php
 *
 */
class iaReMapKeys {
    protected $headerKeys = [];     // ['key1'=>n1, 'key2'=>n2, ...]
    protected $headerKeysLen = 0;
    protected $defaultByKey = [];   // ['key1'=>'default1', 'key3'=>'default3', ...]
    protected $rowKey2Key = [];     // [ 'key1'=>3, 'Key2'=>1, 'WantHeader'=>'header Row Key', ...]
    protected $headerPrepared = []; // ['label1'=>'key1', 'label1_synonym1'=>'key1', ...]
    protected $duplicateHeaders = [];
    protected $lastRowEmpty = true; // last Data row processed was empty?
    /**
     * Maps rowIn[$inKey] to rowsKeyed[$key] using header row to define the mapping
     *
     * @param array $headerKeys ['key1', 'key2', ...]
     * @param array $headerSynonyms ['Synonym1_1'=>'key1', 'Synonym1_2'=>'key1', 'Synonym2_1'=>'key2', ...] default []
     * @param array $defaultByKey ['key2'=>defaultValueKey2,...] default []
     * @return void
     */
    function __construct(array $headerKeys, $headerSynonyms=[], $defaultByKey=[]) {
        $this->headerKeys = array_flip($headerKeys);
        $this->headerKeysLen = count($this->headerKeys);
        $this->prepareInternalData($headerKeys, $headerSynonyms );
        $this->defaultByKey = $defaultByKey;
    }
    /**
     *
     * Sets up mapping rawRow to keyedRow based on $headerRow, fills $this->rowKey2Key
     *
     * @param array $headerRow
     * @return bool has duplicate headers true
     */
    public function mapHeaderRow($headerRow) {
        $this->duplicateHeaders = [];
        $rowKey2Key = [];
        foreach($headerRow as $rowKey => $headerLabel) {
            $label = $this->headerStandardize($headerLabel);
            if(array_key_exists($label, $this->headerPrepared)) {
                if(!isset($rowKey2Key[$this->headerPrepared[$label]])) {
                    $rowKey2Key[$this->headerPrepared[$label]] = $rowKey;
                } else {
                    $this->duplicateHeaders[$rowKey] = $this->headerPrepared[$label];
                }
            }
        }
        $this->rowKey2Key = $rowKey2Key;
        return !empty($this->duplicateHeaders);
    }
    /**
     * Return a list of duplicated headers
     *
     * @return array with duplicated headers ['headerRowKey'=>'label duplicated']
     */
    function getHeadersDuplicated() { return $this->duplicateHeaders; }
    /**
     * Return array mapping header row's keys to desired (wantHeaders)
     *
     * @return array ['headerRowKey' => 'want header', ..]
     */
    function getHeaderMap() { return $this->rowKey2Key; }
    /**
     * Returns missing headers in header row, even those with defaults
     *
     * @return array header keys missing even if the have default values ['missingKeyN',...]
     */
    public function getHeaderMissing() {
        $headersMapped = $this->rowKey2Key;
        $withDefaults = array_diff_key( $this->defaultByKey, $headersMapped) + $headersMapped;
        return array_keys(array_diff_key($this->headerKeys, $withDefaults));
    }
    /**
     * Returns missing headers in header row  and without default values
     *
     * @return array header keys missing and without default values ['missingKeyN',...]
     */
    public function getHeaderMissingWithoutDefaults() {
        return \array_keys(\array_diff_key($this->headerKeys, $this->rowKey2Key));
    }
    /**
     * Returns $rowKeyed values in $rawRow mapped to headersKey, missing keys (not set, null, '') are filled with defaults
     *
     * @param array $rawRow ['valueForKey1','valueForKey2','valueWithOutKey',]
     * @return array ['key1'=>'valueForKey1', ...]
     */
    public function row2key($rawRow) {
        $this->lastRowEmpty = true;
        $rowKeyed = [];
        foreach($this->rowKey2Key as $key => $rowKey) {
            if(isset($rawRow[$rowKey])) {
                $rowKeyed[$key] = Str::strim($rawRow[$rowKey]);
                if(($rowKeyed[$key] === '' || $rowKeyed[$key] === null)) {
                    if(isset($this->defaultByKey[$key])) {
                        $rowKeyed[$key] = $this->defaultByKey[$key];
                    }
                } else {
                    $this->lastRowEmpty = false;
                }
            } elseif(isset($this->defaultByKey[$key])) {
                $rowKeyed[$key] = $this->defaultByKey[$key];
            }
        }
        if(count($rowKeyed) === $this->headerKeysLen) {
            return $rowKeyed;
        }
        return array_diff_key($this->defaultByKey,$rowKeyed) + $rowKeyed;
    }
    /**
     * Returns false if last processed row has Data for headers, else true. Ignores defaults
     *
     * @return boolean true: last read row has no values for headersMapped ie is empty (not considering default values),
     *         false has Data for all headers
     */
    public function isLastRowEmpty() {return $this->lastRowEmpty;}
    /**
     * Returns missing keys from $rowKeyed, defaulted keys not considered missing
     *
     * @param array $rowKeyed
     * @return array header keys missing, and without defaults, in $rowKeyed ['missingKey1'=>n1,...]
     */
    public function getRowKeysMissing($rowKeyed) {
        return array_keys( array_diff_key($this->headerKeys, $rowKeyed) );
    }
    /**
     * Keys missing, null or ''
     *
     * @param array $rowKeyed
     * @return array keys missing or with null, '' values ie ['a','b']
     */
    public function getRowKeysEmpty($rowKeyed) {
        $emptyKeys = [];
        foreach($this->headerKeys as  $key => $inputKey) {
            if(!isset($rowKeyed[$key]) || $rowKeyed[$key] === '' || $rowKeyed[$key] === null) {
                $emptyKeys[] = $key;
            }
        }
        return $emptyKeys;
    }
    /**
     * Prepares $this->headerPrepared array to
     *
     * @param array $header ['key1','key2', ...]
     * @param array $headerSynonyms ['Synonym 1_1'=>'key1', 'Synonym1_2'=>'key1', 'Synonym2_1'=>'key2', ...]
     * @return void
     */
    protected function prepareInternalData($header, $headerSynonyms ) {
        $this->headerPrepared = [];
        foreach($header as $key) {
            $label = Str::strim( str_replace('_', ' ', Str::camel2Words($key) ) );
            $this->headerVariants($key, $label);
        }
        foreach($headerSynonyms as $label => $key) {
            $this->headerVariants($key, $label);
        }
    }
    /**
     * Deduce en plural o singular, el opuesto en que esta.
     *
     * @param string $key
     * @param string $label  palabra a cambiar de singular a plural y viceversa
     * @return void
     */
    protected function headerVariants($key, $label) {
        $this->headerPrepared[$label] = $key;
        $variants =  iaPalabra::variants($this->headerStandardize($label));
        foreach($variants as $word) {
            $this->headerPrepared[$word] = $key;
        }
    }
    /**
     * Makes label header easily comparable
     *
     * @param string $headerLabel
     * @return string $headerLabel in lowercase, unaccented, trimmed, punctuation chars to  spaces, only single spaces.
     */
    protected function headerStandardize($headerLabel) {
        $label = preg_replace("/['\\p{M}]/umS", '', $headerLabel);
        // @codeCoverageIgnoreStart
        if($label === null) {
            $label = str_replace(["'", '"'], '', $headerLabel);
        }
        // @codeCoverageIgnoreEnd
        $label2 = preg_replace("/\\p{P}/umS", ' ', $label);
        // @codeCoverageIgnoreStart
        if($label2 === null) {
            $label2 = str_replace(['.',';',',','!','?','-','_'], ' ', $label);
        }
        // @codeCoverageIgnoreEnd
        return strtolower(URLify::downcode(Str::strim($label2)));
    }
}