Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Edit checkbox from a template #1791

Closed
poitierjohan opened this issue Dec 29, 2019 · 3 comments
Closed

Edit checkbox from a template #1791

poitierjohan opened this issue Dec 29, 2019 · 3 comments
Milestone

Comments

@poitierjohan
Copy link

Hi all,

I have a template document (*.docx) who contains any checkbox.
Actually, I use '<w:sym w:font="Wingdings" w:char="F0A8"/>'

But, is that possible to use the original checkbox and modifying the value ?

I can edit the tag in the field parameters.

Thank you for your answers...

Regards...

@ozilion
Copy link
Contributor

ozilion commented Dec 30, 2019

Check here please #1187

It was for version 12 but i am sure with little workaround you can make it work.

@ozilion
Copy link
Contributor

ozilion commented Dec 31, 2019

Hi
I managed to import my Template checkbox solution in v17 and here is the work around:

Firstly i am using old activx checkbox in my template docx files and set bookmark name for each as variable. Now lets first add some codes to TemplateProcessor.php file:

const SEARCH_LEFT = -1;
const SEARCH_RIGHT = 1;
const SEARCH_AROUND = 0;

Put this three lines to variables sction of files. Then put codes below to the end of file

    /**
     * Process a table row in a template document.
     *
     * @param string $search
     * @param integer $numberOfClones
     * @param mixed $replace (true to clone, or a string to replace)
     * @param bool $incrementVariables
     * @param bool $throwException
     *
     * @return string|false Returns the row cloned or false if the $search macro is not found
     *
     * @throws \PhpOffice\PhpWord\Exception\Exception
     */
    private function processRow(
        $search,
        $numberOfClones = 1,
        $replace = true,
        $incrementVariables = true,
        $throwException = false
    )
    {
        return $this->processSegment(
            static::ensureMacroCompleted($search),
            'w:tr',
            0,
            $numberOfClones,
            'MainPart',
            function (&$xmlSegment, &$segmentStart, &$segmentEnd, &$part) use (&$replace) {
                if (strpos($xmlSegment, '<w:vMerge w:val="restart"')) {
                    $extraRowEnd = $segmentEnd;
                    while (true) {
                        $extraRowStart = $extraRowEnd + 1;
                        $extraRowEnd = strpos($part, '</w:tr>', $extraRowStart);

                        if (!$extraRowEnd) {
                            break;
                        }
                        $extraRowEnd += strlen('</w:tr>');
                        // If tmpXmlRow doesn't contain continue, this row is no longer part of the spanned row.
                        $tmpXmlRow = substr($part, $extraRowStart, ($extraRowEnd - $extraRowStart));
                        if (!preg_match('#<w:vMerge ?/>#', $tmpXmlRow)
                            && !preg_match('#<w:vMerge w:val="continue" ?/>#', $tmpXmlRow)
                        ) {
                            break;
                        }
                        // This row was a spanned row, update $segmentEnd and search for the next row.
                        $segmentEnd = $extraRowEnd;
                    }
                    $xmlSegment = substr($part, $segmentStart, ($segmentEnd - $segmentStart));
                }
                return $replace;
            },
            $incrementVariables,
            $throwException
        );
    }

    /**
     * Clone a table row in a template document.
     *
     * @param string $search
     * @param integer $numberOfClones
     * @param bool $incrementVariables
     * @param bool $throwException
     *
     * @return mixed Returns true if row cloned succesfully or or false if the $search macro is not found
     *
     * @throws \PhpOffice\PhpWord\Exception\Exception
     */
    public function cloneRow(
        $search,
        $numberOfClones = 1,
        $incrementVariables = true,
        $throwException = false
    )
    {
        return $this->processRow($search, $numberOfClones, true, $incrementVariables, $throwException);
    }

    /**
     * process a segment.
     *
     * @param string $needle If this is a macro, you need to add the ${} around it yourself.
     * @param string $xmltag an xml tag without brackets, for example:  w:p
     * @param integer $direction in which direction should be searched. -1 left, 1 right. Default 0: around
     * @param integer $clones How many times the segment needs to be cloned
     * @param string $docPart 'MainPart' (default) 'Footers:1' (first footer) or 'Headers:1' (first header)
     * @param mixed $replace true (default/cloneSegment) false(getSegment) string(replaceSegment) function(callback)
     * @param boolean $incrementVariables true by default (variables get appended #1, #2 inside the cloned blocks)
     * @param boolean $throwException false by default (it then returns false or null on errors).
     *
     * @return mixed The segment(getSegment), false (no $needle), null (no tags), true (clone/replace)
     */
    public function processSegment(
        $needle,
        $xmltag,
        $direction = self::SEARCH_AROUND,
        $clones = 1,
        $docPart = 'MainPart',
        $replace = true,
        $incrementVariables = true,
        $throwException = false
    )
    {
        $docPart = preg_split('/:/', $docPart);
        if (count($docPart) > 1) {
            $part = &$this->{"tempDocument" . $docPart[0]}[$docPart[1]];
        } else {
            $part = &$this->{"tempDocument" . $docPart[0]};
        }
        $needlePos = strpos($part, $needle);

        if ($needlePos === false) {
            return $this->failGraciously(
                "Can not find macro '$needle', text not found or text contains markup.",
                $throwException,
                false
            );
        }

        $directionStart = $direction == self::SEARCH_RIGHT ? 'findOpenTagRight' : 'findOpenTagLeft';
        $directionEnd = $direction == self::SEARCH_LEFT ? 'findCloseTagLeft' : 'findCloseTagRight';
        $segmentStart = $this->{$directionStart}($part, "<$xmltag>", $needlePos, $throwException);
        $segmentEnd = $this->{$directionEnd}($part, "</$xmltag>", $needlePos, $throwException);

        if ($segmentStart >= $segmentEnd && $segmentEnd) {
            if ($direction == self::SEARCH_RIGHT) {
                $segmentEnd = $this->findCloseTagRight($part, "</$xmltag>", $segmentStart);
            } else {
                $segmentStart = $this->findOpenTagLeft($part, "<$xmltag>", $segmentEnd - 1, $throwException);
            }
        }

        if (!$segmentStart || !$segmentEnd) {
            return $this->failGraciously(
                "Can not find <$xmltag> ($segmentStart,$segmentEnd) around segment '$needle'",
                $throwException,
                null
            );
        }

        $xmlSegment = $this->getSlice($part, $segmentStart, $segmentEnd);

        while (is_callable($replace)) {
            $replace = $replace($xmlSegment, $segmentStart, $segmentEnd, $part);
        }
        if ($replace !== false) {
            if ($replace === true) {
                $replace = static::cloneSlice($xmlSegment, $clones, $incrementVariables);
            }
            $part =
                $this->getSlice($part, 0, $segmentStart)
                . $replace
                . $this->getSlice($part, $segmentEnd);
            return true;
        }

        return $xmlSegment;
    }

    /**
     * If $throwException is true, it throws an exception, else it returns $elseReturn
     *
     * @param string $exceptionText
     * @param bool $throwException
     * @param mixed $elseReturn
     *
     * @return mixed
     *
     * @throws \PhpOffice\PhpWord\Exception\Exception
     */
    private function failGraciously($exceptionText, $throwException, $elseReturn)
    {
        if ($throwException) {
            throw new Exception($exceptionText);
        } else {
            return $elseReturn;
        }
    }

    /**
     * Find the start position of the nearest tag before $offset.
     *
     * @param string $searchString The string we are searching in (the mainbody or an array element of Footers/Headers)
     * @param string $tag Fully qualified tag, for example: '<w:p>' (with brackets!)
     * @param integer $offset Do not look from the beginning, but starting at $offset
     * @param boolean $throwException
     *
     * @return integer Zero if not found (due to the nature of xml, your document never starts at 0)
     *
     * @throws \PhpOffice\PhpWord\Exception\Exception
     */

    protected function findOpenTagLeft(&$searchString, $tag, $offset = 0, $throwException = false)
    {
        $tagStart = strrpos(
            $searchString,
            substr($tag, 0, -1) . ' ',
            ((strlen($searchString) - $offset) * -1)
        );

        if ($tagStart === false) {
            $tagStart = strrpos(
                $searchString,
                $tag,
                ((strlen($searchString) - $offset) * -1)
            );

            if ($tagStart === false) {
                return $this->failGraciously(
                    "Can not find the start position of the item to clone.",
                    $throwException,
                    0
                );
            }
        }

        return $tagStart;
    }

    /**
     * Find the start position of the nearest tag before $offset.
     *
     * @param string $searchString The string we are searching in (the mainbody or an array element of Footers/Headers)
     * @param string $tag Fully qualified tag, for example: '<w:p>' (with brackets!)
     * @param integer $offset Do not look from the beginning, but starting at $offset
     * @param boolean $throwException
     *
     * @return integer Zero if not found (due to the nature of xml, your document never starts at 0)
     *
     * @throws \PhpOffice\PhpWord\Exception\Exception
     */

    protected function findOpenTagRight(&$searchString, $tag, $offset = 0, $throwException = false)
    {
        $tagStart = strpos(
            $searchString,
            substr($tag, 0, -1) . ' ',
            $offset
        );

        if ($tagStart === false) {
            $tagStart = strrpos(
                $searchString,
                $tag,
                $offset
            );

            if ($tagStart === false) {
                return $this->failGraciously(
                    "Can not find the start position of the item to clone.",
                    $throwException,
                    0
                );
            }
        }

        return $tagStart;
    }

    /**
     * Find the end position of the nearest $tag after $offset.
     *
     * @param string $searchString The string we are searching in (the MainPart or an array element of Footers/Headers)
     * @param string $tag Fully qualified tag, for example: '</w:p>'
     * @param integer $offset Do not look from the beginning, but starting at $offset
     *
     * @return integer Zero if not found
     */
    protected function findCloseTagLeft(&$searchString, $tag, $offset = 0)
    {
        $pos = strrpos($searchString, $tag, ((strlen($searchString) - $offset) * -1));

        if ($pos !== false) {
            return $pos + strlen($tag);
        } else {
            return 0;
        }
    }


    /**
     * Find the end position of the nearest $tag after $offset.
     *
     * @param string $searchString The string we are searching in (the MainPart or an array element of Footers/Headers)
     * @param string $tag Fully qualified tag, for example: '</w:p>'
     * @param integer $offset Do not look from the beginning, but starting at $offset
     *
     * @return integer Zero if not found
     */
    protected function findCloseTagRight(&$searchString, $tag, $offset = 0)
    {
        $pos = strpos($searchString, $tag, $offset);

        if ($pos !== false) {
            return $pos + strlen($tag);
        } else {
            return 0;
        }
    }

    /**
     * Clone a string and enumerate ( i.e. ${macro#1} )
     *
     * @param string $text Must be a variable as we use references for speed
     * @param integer $numberOfClones How many times $text needs to be duplicated
     * @param bool $incrementVariables If true, the macro's inside the string get numerated
     *
     * @return string
     */
    protected static function cloneSlice(&$text, $numberOfClones = 1, $incrementVariables = true)
    {
        $result = '';
        for ($i = 1; $i <= $numberOfClones; $i++) {
            if ($incrementVariables) {
                $result .= preg_replace('/\$\{(.*?)(\/?)\}/', '\${\\1#' . $i . '\\2}', $text);
            } else {
                $result .= $text;
            }
        }
        return $result;
    }

    /**
     * Get a slice of a string.
     *
     * @param int $startPosition
     * @param int $endPosition
     *
     * @return string
     */

    protected function getSlice(&$searchString, $startPosition, $endPosition = 0)
    {
        if (!$endPosition) {
            $endPosition = strlen($searchString);
        }

        return substr($searchString, $startPosition, ($endPosition - $startPosition));
    }

    public function setCheckboxMS($search, $newValue = true)
    {
        $this->processSegment(
            $search,
            'w:checkBox',
            -1,
            0,
            'MainPart',
            function (&$xmlSegment, &$segmentStart, &$segmentEnd, &$part) use ($newValue) {
                $newValue = $newValue ? 1 : 0;
                $count = 0;
                $xmlSegment = preg_replace(
                    ['~<w:default w:val="[^"]+"(\s?)/>~u'],
                    ['<w:default w:val="' . $newValue . '"\1/>'],
                    $xmlSegment,
                    1,
                    $count
                );
                return $xmlSegment; // replace segment and return the $xmlSegment
            }
        );
    }

After pasting this codes you will have two getSlice and cloneRow functions, since i don't use cloneRow function i commented original ones and using functions above.

Now you are ready to use setCheckboxMS function in your project to change state of checkboxes. To do this all you need to use is:

$doc = new TemplateProcessor($filem);
$doc->setCheckboxMS('w:name="cb_bookmark_name"', true);
or
$doc->setCheckboxMS('w:name="cb_bookmark_name"', false);

home it helps

@Progi1984
Copy link
Member

Fixed by #2509

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.

3 participants