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

Setting checkbox value in template #1187

Closed
ozilion opened this issue Nov 8, 2017 · 21 comments · May be fixed by #1790
Closed

Setting checkbox value in template #1187

ozilion opened this issue Nov 8, 2017 · 21 comments · May be fixed by #1790

Comments

@ozilion
Copy link
Contributor

ozilion commented Nov 8, 2017

I think still this is one of the most wanted feature of Templates. Can we add this too?

@FBnil
Copy link

FBnil commented Nov 11, 2017

@ozilion Can be done already, but how do you imagine this function? Do you free-form search for some text, and find the first checkbox to the left of it? Or do you enumerate the checkboxes and search for the n-th one (...with xslt?)? Search for a w:p (paragraph) where the unique id of the w:sdt tag is a certain value?.. in short: how do YOU say "(un)check THAT one".

<w:sdt>
<w14:checkbox>
    <w14:checked w14:val="1"/>
:
         <w:t>☒</w:t>
      </w:r>
    </w:sdtContent>
</w:sdt>

@ozilion
Copy link
Contributor Author

ozilion commented Nov 12, 2017

I tried PHPDocx, they use bookmark in checkbox. Defining bookmark variable for each checkbox should work or may be better solution? I have a forms contains many checkboxes in but depending some other criteria i just have to check 1 to 3 of them.

For example define bookmark like ${check1} and use in code $template->setChekBox("check", true);

@ozilion
Copy link
Contributor Author

ozilion commented Nov 12, 2017

By the way changing

<w14:checked w14:val="1"/>
to
<w14:checked w14:val="0"/>

will work

@FBnil
Copy link

FBnil commented Nov 13, 2017

I thought, or at least docx4j does, set both the ☒ AND the val="1" (or vice versa, text.setValue("☐"); and val="0").

Bookmarks, huh. Nice unobtrusive way to add variables (did not know it). I'll see what I can do, give me a few days.

@FBnil
Copy link

FBnil commented Nov 13, 2017

Minimal working example. Requires my fork of TemplateProcessor.php (drop-in replacement for 0.13.0/0.14.0, only replace TemplateProcessor.php in your ./vendor/)
@ozilion So yes, totally doable, but lacking in functionality. Do you have more suggestions to build up a forms library?

EDIT: code removed, see branch with production quality code.

note: still contains a flaw that it can not find the last checkbox. This is only a code example, no testcases have been written yet. I might need to update my TemplateProcessor to include search-right-from-tag and search-left-from-tag to lessen the code burden.
Also: <w14:checkedState w:val="2612"/> actually says which unicode chr is required to set the checkbox, so we need to read that value (and not set ☒ hardcoded).

@ozilion
Copy link
Contributor Author

ozilion commented Nov 13, 2017

I think this is the one of the most expected feature (at least for me) and personally i could use convertion to pdf but it is another matter. In our project we only need now is to change state of any checkbox in templates. Thanks for your efforts, you have enough time :)

@FBnil
Copy link

FBnil commented Nov 14, 2017

Conversion to pdf is a whole new bag of cats, as it goes word->html->pdf and html markup is lacking if you want more than just the basics. If you need a perfect word to pdf, either get a commercial version or install the commandline version of open/libreoffice, which just works but is very slow and does not scale (or look into UNO if you need speed; search for "unoconv")

@FBnil
Copy link

FBnil commented Nov 15, 2017

@ozilion Ok, time for you to be my first tester.
From my branch

grab 2 files:
TemplateProcessor.php
CheckboxTemplateProcessor.php
and put them in your ./vendor/phpoffice/PHPWord/src/PhpWord/ directory.

From there (you can read the testcases and source to see what it does)

$file_dir = storage_path('checkbox_withbookmark.docx');
$template = new \PhpOffice\PhpWord\CheckboxTemplateProcessor($file_dir);
$template->setCheckboxOn('bookmark_1');
$template->setCheckboxOff('bookmark_2');
$template->saveAs(storage_path('app/public/tmp.docx'));
return response()->download(storage_path('app/public/tmp.docx'))->deleteFileAfterSend(true);

You also have bool $template->getCheckbox('bookmark_1'); to get the state of a checkbox.

Because CheckboxTemplateProcessor inherits all from my rewrite of TemplateProcessor, you have also the goodies that TemplateProcessor brings (in fact, we use its segments to do the hard work)
You can read the text in my pull request to see what it can do.

I really wish PHPOffice will drop support for PHP5.3.3 soon, because it is better to use traits instead of subclasses. (because you can mix-and-match with traits).

@ozilion
Copy link
Contributor Author

ozilion commented Nov 16, 2017

V13 not working with me, it genereates corrupted files from templates. in v12 i can use it without problem but your files didn't work for me.

@ozilion
Copy link
Contributor Author

ozilion commented Nov 16, 2017

I made it work v13 but $template->setCheckboxOn('bookmark_1'); didn't work. Checkbox still unchecked. But can i see your test word file, may i made a mistake in my template defining bookmark?

@FBnil
Copy link

FBnil commented Nov 16, 2017

The version does not matter much as you are replacing the full TemplateProcessor with something else.
I made sure that corruption should not be a problem anymore (so 0.13.0 with this patch should work fine), are you saying that corruption still occurs with my version of TemplateProcessor? Do you have a docx you can share with the problem?

What did not work? Does $result=$template->setCheckboxOn('bookmark_1'); return false? Did you add the bookmark in MSWord as ${bookmark_1} ? You can also use macro names, just add a macro in the text right to the checkbox.

You should have something like:

<w:p>
<w:pPr><w:pStyle w:val="Normal"/><w:rPr></w:rPr></w:pPr>
<w:sdt>
	<w:sdtPr>
		<w14:checkbox>
			<w14:checked w:val="0"/>
			<w14:checkedState w:val="2612"/>
			<w14:uncheckedState w:val="2610"/>
		</w14:checkbox>
	</w:sdtPr>
	<w:sdtContent>
		<w:r>
			<w:rPr><w:rFonts w:eastAsia="MS Gothic" w:ascii="MS Gothic" w:hAnsi="MS Gothic"/><w:lang w:val="en-US"/></w:rPr>
			<w:t>☐</w:t>
		</w:r>
	</w:sdtContent>
</w:sdt>
<w:r><w:rPr><w:lang w:val="en-US"/></w:rPr><w:t xml:space="preserve"> </w:t></w:r>
<w:bookmarkStart w:id="0" w:name="${bookmark_1}"/><w:bookmarkEnd w:id="0"/>
<w:r><w:rPr><w:lang w:val="en-US"/></w:rPr><w:t xml:space="preserve">This is </w:t></w:r>
<w:r><w:rPr><w:lang w:val="en-US"/></w:rPr><w:t>a unchecked checkbox</w:t></w:r>
<w:r><w:rPr><w:lang w:val="en-US"/></w:rPr><w:t xml:space="preserve"> </w:t></w:r>
<w:r><w:rPr><w:lang w:val="en-US"/></w:rPr><w:t>${line1}</w:t></w:r>
</w:p>

@FBnil
Copy link

FBnil commented Nov 16, 2017

Here is a document you requested

@ozilion
Copy link
Contributor Author

ozilion commented Nov 16, 2017

2.AFR-02BasvuruGozdenGecirme-R5.docx
this is one of my template files, trying to test with check box near 'Kuruluşta tasarım ve geliştirme olmaması'

At first, the created files cannot be opened it was saying corrupted or broken file. Then i managed to create files succesfully but now only my first of about 25 templates replaced but others remains as original template, variables not converting. New TemplateProcessor not working in v12. Lastly I cannot add bookmark with special characters like ${ and }, using Office Pro 365 Plus.

Note: As you see i am using @ instead ${ and }, more suitable for my project.

@FBnil
Copy link

FBnil commented Nov 16, 2017

Ok, there are several ways to tackle this and you need to choose the most logical method.

Your document contains:

<w:fldChar w:fldCharType="begin">
  <w:ffData>
    <w:name w:val="tasarim73"/><w:enabled/><w:calcOnExit w:val="0"/>
    <w:statusText w:type="text" w:val="@tasarim73@"/>
    <w:checkBox><w:sizeAuto/><w:default w:val="0"/></w:checkBox>
  </w:ffData>
</w:fldChar>

We also have your bookmark (which NEEDS to be unique), which is mixed up with another bookmark.

<w:bookmarkStart w:id="1" w:name="tasarim73"/>
	<w:bookmarkStart w:id="2" w:name="_GoBack"/>
<w:bookmarkEnd w:id="1"/>
	<w:bookmarkEnd w:id="2"/>

and we have several copy-pasted, same name:

<w:fldChar w:fldCharType="begin">
  <w:ffData>
    <w:name w:val="Onay1"/><w:enabled/><w:calcOnExit w:val="0"/>
    <w:checkBox><w:sizeAuto/><w:default w:val="0"/></w:checkBox>
  </w:ffData>
</w:fldChar>

Now the LibreOffice implementation is totally different from the MSWord implementation. In fact, I can not manipulate a MSWord checkbox in LibreOffice, and reading your reply, editing a LibreOffice checkbox is not possible in MSWord. Sigh.

Setting your first checkbox, using the w:ffData w:name is done like this:

$file_dir = storage_path('2.AFR-02BasvuruGozdenGecirme-R5.docx');
$template = new \PhpOffice\PhpWord\TemplateProcessor($file_dir);
// \PhpOffice\PhpWord\TemplateProcessor::$ensureMacroCompletion = false;

// quick function that only uses TemplateProcessor
function setCheckboxMS(&$template, $search, $newValue=true){
	$template->processSegment(
		$search,
		'w:checkBox',
		\PhpOffice\PhpWord\TemplateProcessor::SEARCH_LEFT,
		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
		}
	);	
}

setCheckboxMS($template, 'w:name="@tasarim73@"', true); // search on w:statusText
setCheckboxMS($template, 'w:val="tasarim73"', true); // search on w:ffData name value
setCheckboxMS($template, 'w:name="tasarim73"', true); // search on bookmark name
setCheckboxMS($template, 'Çalışanların', true); // search on text (careful, NOT guaranteed to work)
setCheckboxMS($template, 'w:val="Onay1"', true);

Also note that there is no guarantee that a text is continuous, it could be:
<w:t xml:space="preserve">Kuruluşta tasarım </w:t> and the rest of the text is 100 characters to the right, with xml in between. using ${macroname} actually searches and find these (and removes all xml between the macro, sometimes corrupting the document just by opening it with the 0.13.0 TemplateProcessor).
However, searching on bookmarks or uniquely named ffData should always work, as that is kept together by xml.

It will take a while (weekend) to update CheckboxTemplateProcessor to also allow MSWord's version of checkboxes. Meanwhile, a question:

What is the easier setting to search for? Naming the ffData "tasarim73", setting the statusText "@tasarim73@" or adding a bookmark? Because with a bookmark, we find the bookmark and search to the left (SEARCH_LEFT), but with ffData/statusText we have to search to the right (SEARCH_RIGHT)

<w:fldChar w:fldCharType="begin">
  <w:ffData>
    <w:name w:val="tasarim73"/><w:enabled/><w:calcOnExit w:val="0"/>
    <w:statusText w:type="text" w:val="@tasarim73@"/>
    <w:checkBox><w:sizeAuto/><w:default w:val="0"/></w:checkBox>
  </w:ffData>
</w:fldChar>

@FBnil
Copy link

FBnil commented Nov 16, 2017

note: TemplateProcessor has been updated yet again (bug with search direction, which we use, update recommended).

@ozilion
Copy link
Contributor Author

ozilion commented Nov 17, 2017

Thank you very much for quick responses.
I think using <w:name w:val="tasarim73"/><w:enabled/> is better because it's also create bookmark when naming.

@ozilion
Copy link
Contributor Author

ozilion commented Nov 17, 2017

Ok. In Microsoft Office <w:name w:val="tasarim73"/>section is bookmark of checkbox element and <w:statusText w:type="text" w:val="@tasarim73@"/>is help text of checkbox. Also Microsoft
Office does not allow any special charackters in bookmark names.

Whatever we choose it should be fastest and guaranted way for setting checkbox.

If this 'setCheckboxMS($template, 'w:val="Onay1"', true);' will work, Onay1 can be use as defined name and be unique as well.

@ozilion
Copy link
Contributor Author

ozilion commented Nov 17, 2017

Final update;
Using
setCheckboxMS($template, 'w:name="tasarim73"', true);
Worked like i need but to do it I set search direction to 0(around as it says in function head text). Here it is
function setCheckboxMS(&$template, $search, $newValue=true){ $template->processSegment( $search, 'w:checkBox', 0,--> direction 0, 'MainPart',

This way checkboxes i want set correctly and i used PhpWprd v12 with minor hacks. Your code should work directly in v13 without any changes. But this changes, broke other replaces which is, especially all my search values in my templates after chekbox been checked not replacing still remains as template.

@FBnil
Copy link

FBnil commented Nov 17, 2017

@ozilion You mean that variables like @firmaadi@ are not replaced? You need to disable the macro completion, or else it will search for ${@firmaadi@}:

\PhpOffice\PhpWord\TemplateProcessor::$ensureMacroCompletion = false;

(or \PhpOffice\PhpWord\CheckboxTemplateProcessor if you are still using that subclass)

You can also go back to the original TemplateProcessor, and only add the processSegment (and the functions it uses, as they do not have the same name as existing functions.

Search AROUND should not work, you have to use search LEFT to search BEFORE the bookmark, use getSegment() to see if what you are grabbing is actually what you want to grab (or log inside the function to a logfile and dump the $xmlSegment)

@ozilion
Copy link
Contributor Author

ozilion commented Aug 8, 2019

@troosan Any news about this to be added to new versions as default feature?

@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
4 participants