<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rasmus Hurup Hansen</title>
    <description>The latest articles on DEV Community by Rasmus Hurup Hansen (@rhurup).</description>
    <link>https://dev.to/rhurup</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4125911%2F18cfbb3a-1e7d-44d1-9e26-1bc0a8350a8f.png</url>
      <title>DEV Community: Rasmus Hurup Hansen</title>
      <link>https://dev.to/rhurup</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rhurup"/>
    <language>en</language>
    <item>
      <title>Your website is probably italicizing plant names wrong</title>
      <dc:creator>Rasmus Hurup Hansen</dc:creator>
      <pubDate>Tue, 15 Sep 2026 11:04:16 +0000</pubDate>
      <link>https://dev.to/rhurup/your-website-is-probably-italicizing-plant-names-wrong-55g0</link>
      <guid>https://dev.to/rhurup/your-website-is-probably-italicizing-plant-names-wrong-55g0</guid>
      <description>&lt;p&gt;Here is a plant name, formatted the way most websites do it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Hosta 'Blue Angel'&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And here it is formatted correctly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Hosta&lt;/em&gt; 'Blue Angel'&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The difference is small, but it's a rule. Botanical names follow two naming codes - the ICN for wild plants and the ICNCP for cultivated ones - and they are specific about typography:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Italic:&lt;/strong&gt; the genus, the species epithet and infraspecific epithets. &lt;em&gt;Pinus mugo&lt;/em&gt; subsp. &lt;em&gt;uncinata&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Roman (not italic):&lt;/strong&gt; rank markers (&lt;code&gt;subsp.&lt;/code&gt;, &lt;code&gt;var.&lt;/code&gt;, &lt;code&gt;f.&lt;/code&gt;), author citations, the hybrid sign &lt;code&gt;×&lt;/code&gt;, cultivar groups - and cultivar names, which go in single quotes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;em&gt;Hydrangea&lt;/em&gt; ×&lt;em&gt;macrophylla&lt;/em&gt; subsp. &lt;em&gt;serrata&lt;/em&gt; 'Bluebird' has three italic words and three roman parts. Wrapping the whole string in &lt;code&gt;&amp;lt;i&amp;gt;&lt;/code&gt; gets it wrong - and that is what most sites do. Ours too: we noticed it on &lt;a href="https://plantgeekz.com" rel="noopener noreferrer"&gt;PlantGeekz&lt;/a&gt;, our plant app, while building the library this post is about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The input is worse than the output
&lt;/h2&gt;

&lt;p&gt;Getting the italics right is the easy half. The hard half is that plant names arrive from spreadsheets, nursery catalogues and user input in every shape imaginable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hydrangea x macrophylla ssp. serrata cv. Bluebird
HYDRANGEA ×MACROPHYLLA
Hosta ‘Blue Angel’          ← curly quotes from iOS
Mentha x piperita L.
Brassica oleracea (Capitata Group) 'Brunswick'
Rosa Flower Carpet® 'Noare'
Salix alba × S. fragilis   ← a hybrid formula, not a species
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;x&lt;/code&gt; should be &lt;code&gt;×&lt;/code&gt;. &lt;code&gt;ssp.&lt;/code&gt; should be &lt;code&gt;subsp.&lt;/code&gt;. &lt;code&gt;cv. Bluebird&lt;/code&gt; should be &lt;code&gt;'Bluebird'&lt;/code&gt;. And you need to know which part is which before you can italicize anything.&lt;/p&gt;

&lt;p&gt;There are great scientific name parsers - &lt;a href="https://github.com/gnames/gnparser" rel="noopener noreferrer"&gt;gnparser&lt;/a&gt; in Go and &lt;a href="https://github.com/gbif/name-parser" rel="noopener noreferrer"&gt;GBIF's name-parser&lt;/a&gt; in Java - but nothing small that runs in a PHP web app or directly in the browser. So we built one.&lt;/p&gt;

&lt;h2&gt;
  
  
  botanical-name
&lt;/h2&gt;

&lt;p&gt;It's a zero-dependency library, published for &lt;a href="https://github.com/plantgeekz/botanical-name-php" rel="noopener noreferrer"&gt;PHP&lt;/a&gt; and &lt;a href="https://github.com/plantgeekz/botanical-name-js" rel="noopener noreferrer"&gt;JavaScript/TypeScript&lt;/a&gt;. Both packages give byte-identical results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toHtml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@plantgeekz_com/botanical-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hydrangea x macrophylla ssp. serrata cv. Bluebird&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Hydrangea ×macrophylla subsp. serrata 'Bluebird'&lt;/span&gt;

&lt;span class="nf"&gt;toHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// &amp;lt;i&amp;gt;Hydrangea&amp;lt;/i&amp;gt; ×&amp;lt;i&amp;gt;macrophylla&amp;lt;/i&amp;gt; subsp. &amp;lt;i&amp;gt;serrata&amp;lt;/i&amp;gt; &amp;amp;#039;Bluebird&amp;amp;#039;&lt;/span&gt;

&lt;span class="nf"&gt;key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// hydrangea macrophylla subsp. serrata bluebird&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same in PHP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;PlantGeekz\BotanicalName\BotanicalName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BotanicalName&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hydrangea x macrophylla ssp. serrata cv. Bluebird'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Hydrangea ×macrophylla subsp. serrata 'Bluebird'&lt;/span&gt;
&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;toHtml&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// &amp;lt;i&amp;gt;Hydrangea&amp;lt;/i&amp;gt; ×&amp;lt;i&amp;gt;macrophylla&amp;lt;/i&amp;gt; subsp. &amp;lt;i&amp;gt;serrata&amp;lt;/i&amp;gt; &amp;amp;#039;Bluebird&amp;amp;#039;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;parse()&lt;/code&gt; gives you the parts - genus, epithet, authors, infraspecific ranks, group, trade designation, cultivar - and a list of warnings for everything it had to fix, so you can flag messy data instead of silently changing it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Rosa canina ssp. canina&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ['rank_normalized']&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;key()&lt;/code&gt; strips authors, quotes, hybrid signs and diacritics, which makes it handy for matching and de-duplicating names from different sources.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 30,569 real names taught us
&lt;/h2&gt;

&lt;p&gt;We wrote the rules first and a set of hand-made test cases second. Everything passed. Then we ran both implementations over 30,569 real names from the PlantGeekz taxonomy - species with authors and thousands of cultivars - and checked that every clean name came back unchanged.&lt;/p&gt;

&lt;p&gt;Three things broke that our own test cases had never covered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Apostrophes inside cultivar names.&lt;/strong&gt; To find a cultivar, you look for text in single quotes. A lazy regex stops at the first closing quote, and that works for &lt;code&gt;'Blue Angel'&lt;/code&gt;. It falls apart on the names daylily and dahlia breeders actually register:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dahlia 'Rees' Choice'
Hemerocallis 'Burnin' Down the House'
Dahlia 'Hugs 'N' Kisses'
Hemerocallis 'Tam O' Shanter'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix was to match greedily - from the first opening quote to the last closing one - which, it turned out, our own backend was already doing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A capital X inside a name.&lt;/strong&gt; &lt;code&gt;Hemerocallis 'Longfields X Factor'&lt;/code&gt; was read as a hybrid formula - &lt;em&gt;Hemerocallis 'Longfields&lt;/em&gt; crossed with &lt;em&gt;Factor'&lt;/em&gt; - because &lt;code&gt;X&lt;/code&gt; between two capitalised words is how formulas are written. The formula detector now tracks whether it is inside quotes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A name in brackets.&lt;/strong&gt; Seed catalogues write cultivar groups as &lt;code&gt;Brassica oleracea (Capitata)&lt;/code&gt;, so we treated a trailing name in brackets as a group. In the real data, &lt;code&gt;Cyathea minervae (Lehnert)&lt;/code&gt; and &lt;code&gt;Justicia carrissoi (Benoist)&lt;/code&gt; turned out to be incomplete author citations, not groups. Now a bare bracket only counts as a group when a cultivar follows it.&lt;/p&gt;

&lt;p&gt;None of these showed up in the tests we wrote ourselves. All three showed up within minutes on real data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping PHP and TypeScript identical
&lt;/h2&gt;

&lt;p&gt;Two implementations of the same parser will drift apart, so we set up three guards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared fixtures.&lt;/strong&gt; The PHP repo owns &lt;code&gt;fixtures/names.json&lt;/code&gt; - currently 60+ cases with the expected parts, text, HTML, slug and key. The JS repo keeps a copy, and CI fails if the copy differs from the PHP one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A line-by-line port.&lt;/strong&gt; The TypeScript parser mirrors the PHP one method for method, including the parts that differ between the languages. JavaScript's &lt;code&gt;\s&lt;/code&gt; and &lt;code&gt;trim()&lt;/code&gt; match more Unicode whitespace than PHP's do, for instance, so both use the same explicit character class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Differential testing.&lt;/strong&gt; Before release, the 30,569 names went through both implementations and the full output was compared byte for byte. Zero differences, including after each of the three fixes above.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Scope, honestly
&lt;/h2&gt;

&lt;p&gt;It's a structural parser. It reads the shape of a name and never looks it up, so it can tell you what the parts are but not whether the name exists or is accepted. For that you need a taxonomic backbone such as &lt;a href="https://www.gbif.org" rel="noopener noreferrer"&gt;GBIF&lt;/a&gt;. It also doesn't handle zoological or bacterial names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require plantgeekz/botanical-name
npm &lt;span class="nb"&gt;install&lt;/span&gt; @plantgeekz_com/botanical-name
deno add jsr:@plantgeekz/botanical-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is MIT-licensed on GitHub (&lt;a href="https://github.com/plantgeekz/botanical-name-php" rel="noopener noreferrer"&gt;PHP&lt;/a&gt;, &lt;a href="https://github.com/plantgeekz/botanical-name-js" rel="noopener noreferrer"&gt;JS/TS&lt;/a&gt;). If you have a plant name it gets wrong, open an issue with the name, and it will go into the fixtures.&lt;/p&gt;

&lt;p&gt;We built this for &lt;a href="https://plantgeekz.com" rel="noopener noreferrer"&gt;PlantGeekz&lt;/a&gt;, our plant identification and collection app. If you grow plants yourself, have a look.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>php</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
