Go to main content

Textpattern CMS support forum

You are not logged in. Register | Login | Help

#1 2013-09-04 19:20:58

johnstephens
Plugin Author
From: Woodbridge, VA
Registered: 2008-06-01
Posts: 999
Website

From PHP to txp:plugin -- need help troubleshooting!

I have a database full of articles marked up in XHTML. Our application uses Prince XML to generate PDFs using Textpattern URLs. An artifact of that is that footnotes are marked up inline, using the following pattern:

<p>Some paragraph text<span class="fnt">This is the text of a footnote</span>.</p>

Prince replaces every span.fnt with a numeric footnote marker, and renders the enclosed text as a footnote at the bottom of the page.

We want to render the same content in ebook formats, and XHTML is a great starting point, but the inline footnotes are terrible. What I want to do is convert the footnotes to endnotes in my ebook build URL, using a Textpattern plugin.

(I’m aware that endnotes are supported brilliantly by Textile, but I have been unable to persuade the editor to use Textile for article markup. Also, we have additional fields that must be parsed for footnotes/endnotes, which would affect footnote numbering, and the Prince XML software requires this format to generate it’s footnotes on the appropriate page.)

I’m not PHP scripting wizard, but I found some helpful code examples and got this version working:

<?php

class Endnoter
{
  private $number_of_notes = 0;
  private $footnote_texts = array();

  public function replace($input) {

    return preg_replace_callback('#<span class="fnt">(.*)</span>#i', array($this, 'replace_callback'), $input);

  }

  protected function replace_callback($matches) {

    $this->footnote_texts[] = $matches[1];

    return '<sup><a href="#endnote_'.(++$this->number_of_notes).'" id="marker_'.$this->number_of_notes.'">'.$this->number_of_notes.'</a></sup>';

  }

  public function getEndnotes() {
    $out = array();
    $out[] = '<ol>';

    $i = 0;
    foreach($this->footnote_texts as $text) {
      $i++;
      $out[] = '<li id="endnote_'.$i.'">'.$text.' <a href="#marker_'.$i.'">^</a></li>';
    }

    $out[] = '</ol>';

    return implode("\n", $out);
  }

}

// ...

$endnoter = new Endnoter();
echo $endnoter->replace($content);
echo $endnoter->replace($more_content);
echo $endnoter->getEndnotes();

I tried adding the first bit to the head of the relevant Textpattern page, but when I try to use the function in a form, it doesn’t work:

<txp:variable name="content_to_parse">
Some content with<span class="fnt">footnotes</span>
</txp:variable>
<txp:php>
global $endnoter, $variable;
echo $endnoter->replace($variable['content_to_parse']);
</txp:php>

This seems like the perfect case for a basic enclosing plugin function. Following the instructions in Textpattern Solutions (p303), I translated my first cut PHP into the following:

# --- BEGIN PLUGIN CODE ---
global $pax_endnote_no, $pax_endnotes;
if ($pax_endnote_no == NULL) {
  $pax_endnote_no = 0;
}
if ($pax_endnotes == NULL) {
  $pax_endnotes = array();
}

function pax_endnoter($thing = NULL) {
  function pax_endnoter_callback($matches) {
    $pax_endnotes[] = $matches[1];

    return '<sup><a href="#endnote_'.(++$pax_endnote_no).'" id="marker_'.$pax_endnote_no.'">'.$pax_endnote_no.'</a></sup>';

  }
  return preg_replace_callback('#<span class="fnt">(.*)</span>#i', 'pax_endnoter_callback', $thing);

}

function pax_get_endnotes() {
  $out = array();
  $out[] = '<ol>';

  $i = 0;
  foreach($pax_endnotes as $text) {
    $i++;
    $out[] = '<li id="endnote_'.$i.'">'.$text.' <a href="#marker_'.$i.'">^</a></li>';
  }

  $out[] = '</ol>';

  return implode("\n", $out);
}
# --- END PLUGIN CODE ---

Needless to say, I’m not quite there yet. When I call <txp:pax_endnoter>content to parse</txp:pax_endnoter>, the plugin finds the footnote markers alright, but it replaces each instance with Array instead of the linked endnote marker. When I call <txp:pax_get_endnotes/>, I get the alert Undefined variable: pax_endnotes.

It feels like I’m close to cracking it, I just don’t know what to do to resolve these errors. I appreciate any guidance you more seasoned PHP hackers and experts might offer. Any ideas?

Thanks in advance!

Last edited by johnstephens (2013-09-04 19:32:24)

Offline

#2 2013-09-04 21:12:44

etc
Developer
Registered: 2010-11-11
Posts: 5,028
Website GitHub

Re: From PHP to txp:plugin -- need help troubleshooting!

johnstephens wrote:

When I call <txp:pax_get_endnotes/>, I get the alert Undefined variable: pax_endnotes.

I guess you should declare it as global inside the function too. Or use etc_query:

<txp:etc_query data='<p>Some content with<span class="fnt">footnotes</span> and<span class="fnt"> more footnotes</span></p>'
	query="//span[@class='fnt']"
	replace="&=<sup><a href='#endnote_{#row}' id='marker_{#row}'>{#row}</a></sup>"
>
	<txp:variable name="links">
		<txp:variable name="links" />
		<li id="endnote_{#row}">{text()}<a href="#marker_{#row}">^</a></li>
	</txp:variable>
	{$=({#row}|{#rows}).?({//body/*})}
</txp:etc_query>

<ol><txp:variable name="links" /></ol>

Last edited by etc (2013-09-04 21:14:25)

Offline

#3 2013-09-05 14:13:29

johnstephens
Plugin Author
From: Woodbridge, VA
Registered: 2008-06-01
Posts: 999
Website

Re: From PHP to txp:plugin -- need help troubleshooting!

Oleg, this is amazing—thank you!

This almost solves the problem entirely. I need to parse multiple separate areas of my page, and this code resets the footnote numbering each time it is called.

<txp:if_variable name="format" value="ebook">
<!-- EBOOK -->
<txp:variable name="content">
Content with <span class="fnt">Footnotes</class>
</txp:variable>
<txp:if_variable name="format" value="ebook">
<txp:etc_query
  data='<txp:variable name="content"/>'
  query="//span[@class='fnt']"
  replace='&=<sup><a href="#endnote_{#row}x<txp:article_id/>" id="marker_{#row}x<txp:article_id/>">{#row}</a></sup>'
>
<txp:variable name="endnotes">
<txp:variable name="endnotes"/>
<li id='endnote_{#row}x<txp:article_id/>'>{text()}<a href='#marker_{#row}x<txp:article_id/>'>^</a></li>
</txp:variable>
{$=({#row}|{#rows}).?({//body/*})}
</txp:etc_query>
<txp:else/>
<!-- NOT EBOOK -->
<txp:variable name="content"/>
</txp:if_variable>

<!-- LATER... -->

<txp:if_variable name="format" value="ebook">
<!-- EBOOK -->
<txp:if_variable name="format" value="ebook">
<txp:etc_query
  data='<txp:body/>'
  query="//span[@class='fnt']"
  replace='&=<sup><a href="#endnote_{#row}x<txp:article_id/>" id="marker_{#row}x<txp:article_id/>">{#row}</a></sup>'
>
<txp:variable name="endnotes">
<txp:variable name="endnotes"/>
<li id='endnote_{#row}x<txp:article_id/>'>{text()}<a href='#marker_{#row}x<txp:article_id/>'>^</a></li>
</txp:variable>
{$=({#row}|{#rows}).?({//body/*})}
</txp:etc_query>
<txp:else/>
<!-- NOT EBOOK -->
<txp:body/>
</txp:if_variable>

<!-- LATER... -->

<txp:if_variable name="format" value="ebook">
<div id="endnotes">
<h2>Endnotes</h2>
<ol>
<txp:variable name="endnotes"/>
</ol>
</div> <!-- EO #endnotes -->
</txp:if_variable>

The list of endnotes at the end includes notes from all the previous etc_query content, but the numbering goes back to 1 with each call to etc_query. Is there an easy way to persist the numbering?

Offline

#4 2013-09-05 14:29:19

maniqui
Member
From: Buenos Aires, Argentina
Registered: 2004-10-10
Posts: 3,070
Website

Re: From PHP to txp:plugin -- need help troubleshooting!

Just guessing, as I can’t totally follow the code: What if you replace {#row} with a global txp:variable that gets incremented on each loop?

Oleg, I’m curious about this obscure line {$=({#row}|{#rows}).?({//body/*})}. Could you explain it in plain English? :)


La música ideas portará y siempre continuará

TXP Builders – finely-crafted code, design and txp

Offline

#5 2013-09-05 14:49:33

etc
Developer
Registered: 2010-11-11
Posts: 5,028
Website GitHub

Re: From PHP to txp:plugin -- need help troubleshooting!

johnstephens wrote:

The list of endnotes at the end includes notes from all the previous etc_query content, but the numbering goes back to 1 with each call to etc_query.

Ah yes, sorry. There is a way (quickly tested), but it looks scary:

<!-- create a "parser" form, for convenience -->

<txp:if_variable name="count"><txp:else /><txp:variable name="count" value="0" /></txp:if_variable>

<txp:etc_query data='<txp:yield />' query="//span[@class='fnt']" globals="variable" specials="replace,content"
	replace="&=<sup><a href='#endnote_{$+({?count}|{#row})}' id='marker_{$+({?count}|{#row})}'>{$+({?count}|{#row})}</a></sup>"
>
	<txp:variable name="links">
		<txp:variable name="links" />
		<li id="endnote_{$+({?count}|{#row})}">{text()}<a href="#{$+({?count}|{#row})}">^</a></li>
	</txp:variable>
	{$=({#row}|{#rows}).?({//body/node()}<txp:variable name="count" value="{$+({?count}|{#row})}" />)}
</txp:etc_query>

<!-- end of "parser" form -->


<!-- now call it where needed -->

<txp:output_form form="parser">
Content with <span class="fnt">Footnotes</class>
</txp:output_form>
...
<txp:output_form form="parser">
Another content with <span class="fnt">Footnotes</class>
</txp:output_form>
...
<ol><txp:variable name="links" /></ol>

maniqui wrote:

What if you replace {#row} with a global txp:variable that gets incremented on each loop?

How do you know I’m on it right now? :) Currently this will not work, because replace is parsed only once (at the beginning), but just leave it with me for few hours.

Oleg, I’m curious about this obscure line {$=({#row}|{#rows}).?({//body/*})}. Could you explain it in plain English? :)

if(#row == #rows) //last row
	{//body/*} //output the whole data, that was wrapped in <body> tag by php dom.

Last edited by etc (2013-09-05 18:16:42)

Offline

#6 2013-09-05 18:10:51

etc
Developer
Registered: 2010-11-11
Posts: 5,028
Website GitHub

Re: From PHP to txp:plugin -- need help troubleshooting!

With the latest -dev version, the “parser” form is a bit more readable:

<txp:if_variable name="count"><txp:else /><txp:variable name="count" value="1" /></txp:if_variable>


<txp:etc_query data='<txp:yield />' query="//span[@class='fnt']" specials="content"
	replace="&=<sup><a href='#endnote_<txp:variable name=""count"" />x<txp:article_id />' id='marker_<txp:variable name=""count"" />x<txp:article_id />'><txp:variable name=""count"" /></a></sup>"
>
	<txp:variable name="links"><txp:variable name="links" />
		<li id="endnote_<txp:variable name='count' />x<txp:article_id />">{text()}<a href="#endnote_<txp:variable name='count' />x<txp:article_id />">^</a></li>
	</txp:variable>
	{$=({#row}|{#rows}).?({//body/node()})}
	<txp:variable name="count" value="{$+({?count}|{#row})}" />
</txp:etc_query>

Edit: added article id.

Last edited by etc (2013-09-23 08:12:46)

Offline

#7 2013-09-06 10:05:09

etc
Developer
Registered: 2010-11-11
Posts: 5,028
Website GitHub

Re: From PHP to txp:plugin -- need help troubleshooting!

And here is your plugin code:

global $pax_endnote_no, $pax_endnotes;
$pax_endnote_no = 0;
$pax_endnotes = array();

function pax_endnoter_callback($matches) {
  global $pax_endnote_no, $pax_endnotes;
  $pax_endnotes[] = $matches[1];
  return '<sup><a href="#endnote_'.(++$pax_endnote_no).'" id="marker_'.$pax_endnote_no.'">'.$pax_endnote_no.'</a></sup>';
}

function pax_endnoter($atts, $thing = NULL) {
  return preg_replace_callback('#<span class="fnt">(.*)</span>#Ui', 'pax_endnoter_callback', parse($thing));
}

function pax_get_endnotes() {
  global $pax_endnote_no, $pax_endnotes;
  $out = array();
  $out[] = '<ol>';
  $i = 0;
  foreach($pax_endnotes as $text) {
    $i++;
    $out[] = '<li id="endnote_'.$i.'">'.$text.' <a href="#marker_'.$i.'">^</a></li>';
  }
  $out[] = '</ol>';
  return implode("\n", $out);
}

Last edited by etc (2013-09-06 13:16:17)

Offline

#8 2013-09-06 12:41:07

Gocom
Developer Emeritus
From: Helsinki, Finland
Registered: 2006-07-14
Posts: 4,533
Website

Re: From PHP to txp:plugin -- need help troubleshooting!

Those runtime isset() checks are unnecessary, and pax_endnoter() must pass $thing through parser().

Offline

#9 2013-09-06 13:18:31

etc
Developer
Registered: 2010-11-11
Posts: 5,028
Website GitHub

Re: From PHP to txp:plugin -- need help troubleshooting!

Gocom wrote:

pax_endnoter() must pass $thing through parser().

Yes, thanks.

Offline

#10 2013-09-09 18:07:39

johnstephens
Plugin Author
From: Woodbridge, VA
Registered: 2008-06-01
Posts: 999
Website

Re: From PHP to txp:plugin -- need help troubleshooting!

Thanks again, Oleg! Your etc_query suggestions entirely solved my problem! Someone told me I should not use regular expressions for XHTML traversal, and I have abandoned pax_endnoter.

I adapted your code using the current version of etc_query before you posted the dev version. Is there a good reason to use the dev version instead, apart from readability in my TXP form?

Offline

#11 2013-09-12 19:17:55

etc
Developer
Registered: 2010-11-11
Posts: 5,028
Website GitHub

Re: From PHP to txp:plugin -- need help troubleshooting!

johnstephens wrote:

Thanks again, Oleg! Your etc_query suggestions entirely solved my problem! Someone told me I should not use regular expressions for XHTML traversal, and I have abandoned pax_endnoter.

Glad it works, John, DOM parsing is more reliable indeed.

I adapted your code using the current version of etc_query before you posted the dev version. Is there a good reason to use the dev version instead, apart from readability in my TXP form?

No, especially because the idea of attributes reparsing was quickly dropped since. However, trying to optimally solve your problem, I had some alternative ideas that are implemented in the latest version now. One of them is the possibility to save the transformed DOM tree to quickly reuse it later. It works like this:

<!-- import all elements to process in one dom tree -->

<txp:etc_query data='<div><txp:variable name="content"/></div>
		<div><txp:body /></div>' 
	query="//span[@class='fnt']" save="endnotes"
	replace="&=<sup><a href='#endnote_{#row}' id='marker_{#row}'>{#row}</a></sup>"
>
	<txp:variable name="endnotes">
		<txp:variable name="endnotes" />
		<li id="endnote_{#row}">{text()}<a href="#marker_{#row}">^</a></li>
	</txp:variable>
</txp:etc_query>

<!-- now retrieve what needed where needed -->

<txp:etc_query markup="data" data="endnotes" query="div[1]/node()" />
...
<txp:etc_query markup="data" data="endnotes" query="div[2]/node()" />
...
<ol><txp:variable name="endnotes" /></ol>

That’s more readable and flexible, without significant performance drop.

Last edited by etc (2013-09-12 19:59:44)

Offline

#12 2017-06-02 18:50:30

johnstephens
Plugin Author
From: Woodbridge, VA
Registered: 2008-06-01
Posts: 999
Website

Re: From PHP to txp:plugin -- need help troubleshooting!

Oh, dear. I humbly ask for some help with this strange issue.

Some time ago, @etc helped me cook up a way to parse the body text of an article for my PrinceXML footnote markup to output the footnotes as an ordered list of endnotes.

This solution has worked with everything we threw at it. But I never imagined giving it an article text that did not contain footnote markup.

Here’s the code, at the time of this writing:

<txp:if_variable name="endnote_no">

  <txp:else />

  <txp:variable name="endnote_no" value="0"/>

</txp:if_variable>


<txp:etc_query
  data='<txp:body />'
  globals="variable"
  query="//span[@class='fnt']"
  replace='&=<sup><a href="#endnote_{$+({?endnote_no}|{#row})}x<txp:article_id/>" id="marker_{$+({?endnote_no}|{#row})}x<txp:article_id/>">{$+({?endnote_no}|{#row})}</a></sup>'
  specials="replace,content"
  >

  <txp:variable name="endnotes">

    <txp:variable name="endnotes"/>

    <li id='endnote_{$+({?endnote_no}|{#row})}x<txp:article_id/>'>{text()} <a href='#marker_{$+({?endnote_no}|{#row})}x<txp:article_id/>'>^</a></li>

  </txp:variable>

  {$=({#row}|{#rows}).?({//body/node()}<txp:variable name="endnote_no" value="{$+({?endnote_no}|{#row})}" />)}

</txp:etc_query>

This works perfectly, as long as the <txp:body /> text contains the pattern etc_query is looking for<span class='fnt'>Footnote text here</span>.

But if the body contains no footnotes, the output is empty. Same behavior using versions 1.3.3 and 1.2.7 of etc_query.

The trouble is, I need it to return the body text even when it doesn’t contain any footnotes.

@etc, I know you’re a busy person. Can you offer any guidance?

Thank you!

Edit: Fix textile.

Last edited by johnstephens (2017-06-02 18:56:24)

Offline

Board footer

Powered by FluxBB