{"id":1010,"date":"2020-05-11T02:17:55","date_gmt":"2020-05-11T09:17:55","guid":{"rendered":"https:\/\/www.skierpage.com\/blog\/?p=1010"},"modified":"2021-11-09T19:23:36","modified_gmt":"2021-11-10T03:23:36","slug":"web-book-reviews-yet-again","status":"publish","type":"post","link":"https:\/\/www.skierpage.com\/blog\/2020\/05\/web-book-reviews-yet-again\/","title":{"rendered":"web: book reviews again"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">I have a substantial pile of books I&#8217;ve read that will injure me in an earthquake. I ought to write perspicacious pithy reviews of them. I could write them on Amazon, but why should Amazon own and profit from my words? I could write them on <a href=\"https:\/\/lib.reviews\/\">https:\/\/lib.reviews\/<\/a> &#8220;a free, open and not-for-profit platform for reviewing absolutely anything, in any language,&#8221; but it seems a bit moribund. Instead I have this web site! Putting book reviews here will ensure they live forever in complete obscurity.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"oh-no-not-the-semantic-web-again\">Oh no, not the semantic web again!<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A long time ago I simply <a href=\"\/blog\/2006\/08\/books-recent-books.html\">wrote a definition list<\/a> in HTML in Blogger with each book title followed by a paragraph underneath. Then the idea of a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Semantic_Web\">semantic web<\/a> came along: the web page should unambiguously tell machines that a chunk of writing is a <em>review<\/em> of a particular book rather than me advertising some books for sale, or writing about the author. And it should tell the machines it&#8217;s a review by skierpage, of a book with a particular title and ISBN, who gives it a rating of 3 out of 5 stars, etc.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"why-bother\">Why bother?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Disclaimer: all the semantic web work below is probably irrelevant. If your web page is important according to Google&#8217;s PageRank algorithm, then Google will devote AI to figuring out what it says, even if it has no, or incorrect, semantic markup. So most of those making the effort to do this semantic markup are shady SEO (search engine optimization) sites, trying to convince you that if you jump through all these hoops or pay them to do it, then your site on topic X will somehow rise in search results; from utter obscurity on the 20th page of search results to mostly ignored on the 4th page.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"hreview-microformat\">hReview microformat<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Back in 2011 the leading implementation of this idea for plain web pages was <a href=\"https:\/\/microformats.org\/\">microformats<\/a>: you probably already have the relevant pieces of text in your human-readable book review, so put additional markup (the &#8216;M&#8217; in Hypertext Markup Language) around them identifying the bit that&#8217;s the rating, the summary, etc. using invisible HTML attributes like <code>class=reviewer<\/code>, <code>class=rating<\/code>, <code>class=summary<\/code> , etc. So I wrote <a href=\"\/blog\/2011\/04\/books-a-dull-book-about-design\/\">a<\/a> <a href=\"\/blog\/2011\/04\/books-richard-fords-dispassionate-adultery-misses-his-peak\/\">few<\/a> <a href=\"\/blog\/2011\/05\/books-sex-and-science-fiction\/\">reviews<\/a> using an <a href=\"https:\/\/microformats.org\/code\/hreview\/creator\">online tool<\/a> to generate the necessary HTML, which I pasted into WordPress.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"so-many-schemas\">So many schemas<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The hReview microformat is still going and supposedly Google still parses it when it crawls web pages. Some big guns of Web 2.0 (Google, Microsoft, Yahoo, and Yandex) came up with their own standard for structured data, similar but different, at the poorly named <a href=\"https:\/\/schema.org\">schema.org<\/a>: &#8220;a collaborative, community activity with a mission to create, maintain, and promote schemas for structured data on the Internet, on web pages, in email messages, and beyond.&#8221; This got more detailed and complicated than microformats: there are separate related schemas for a <a href=\"https:\/\/schema.org\/Review\">review<\/a> by the <a href=\"https:\/\/schema.org\/Person\">person<\/a> skierpage about a <a href=\"https:\/\/schema.org\/Book\">book<\/a> authored by another person. And there are three ways you can put the machine-readable information into your web pages (two too many!).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Google provides a <a href=\"https:\/\/www.google.com\/webmasters\/markup-helper\/\">structured data markup helper<\/a> to guide me in creating this markup, and then its <a href=\"https:\/\/search.google.com\/structured-data\/testing-tool\">structured data testing tool<\/a> to see if I got it right. (There was another schema generator at tools.seochat.com now defunct, and other checkers at <a href=\"http:\/\/linter.structured-data.org\/\">linter.structured-data.org\/<\/a> , https:\/\/jsonschemalint.com\/ , etc.) If you choose to put invisible markup in the page surrounding the text of your review (schema.org calls this &#8220;microdata,&#8221; different from &#8220;microformat&#8221;), the HTML looks something like:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">&lt;!-- Microdata markup added by Google Structured Data Markup Helper. --&gt;\n  &lt;div itemscope itemtype=\"<a>https:\/\/schema.org\/Book<\/a>\" id=\"<a>hreview-Sprawling,-very-good!<\/a>\"&gt;\n  &lt;meta itemprop=\"<a>isbn<\/a>\" content=\"<a>03-5091234-034<\/a>\"&gt;\n  &lt;meta itemprop=\"<a>genre<\/a>\" content=\"<a>Science Fiction<\/a>\"&gt;\n  &lt;meta itemprop=\"<a>datePublished<\/a>\" content=\"<a>2017-06-04<\/a>\"&gt;\n  &lt;h3&gt;Sprawling, very good!&lt;\/h3&gt;\n  &lt;p&gt;\n    &lt;img itemprop=\"<a>image<\/a>\" class=\"<a>photo<\/a>\" src=\"<a href=\"https:\/\/ecx.images-amazon.com\/images\/I\/51Gvu3UlqGL.jpg\">https:\/\/ecx.images-amazon.com\/images\/I\/51Gvu3UlqGL.jpg<\/a>\" width=\"<a>167<\/a>\" height=\"<a>250<\/a>\" alt=\"<a>cover of 'River of Gods'<\/a>\" align=\"<a>left<\/a>\" style=\"<a>margin-right: 1em<\/a>\"\/&gt;\n  &lt;\/p&gt;\n\n  &lt;div class=\"<a>item<\/a>\"&gt;\n    &lt;a title=\"<a>paperback at Amazon<\/a>\" href=\"<a href=\"https:\/\/www.amazon.com\/River-Gods-Ian-McDonald\/dp\/1591025958\">https:\/\/www.amazon.com\/River-Gods-Ian-McDonald\/dp\/1591025958<\/a>\" class=\"<a>fn url<\/a>\"&gt;\n      &lt;span itemprop=\"<a>name<\/a>\"&gt;River of Gods&lt;\/span&gt;\n    &lt;\/a&gt;\n    by\n    &lt;a href=\"<a href=\"https:\/\/en.wikipedia.org\/wiki\/Ian_McDonald_%28British_author%29\">https:\/\/en.wikipedia.org\/wiki\/Ian_McDonald_%28British_author%29<\/a>\"&gt;\n      &lt;span itemprop=\"<a>author<\/a>\" itemscope itemtype=\"<a>http:\/\/schema.org\/Person<\/a>\"&gt;\n        &lt;span itemprop=\"<a>name<\/a>\"&gt;Ian McDonald&lt;\/span&gt;\n      &lt;\/span&gt;\n    &lt;\/a&gt;\n  &lt;\/div&gt;\n  &lt;p itemprop=\"<a>review<\/a>\" itemscope itemtype=\"<a href=\"https:\/\/schema.org\/Review\">https:\/\/schema.org\/Review<\/a>\" class=\"<a>description<\/a>\"&gt;\n    &lt;abbr itemprop=\"<a>reviewRating<\/a>\" itemscope itemtype=\"<a href=\"https:\/\/schema.org\/Rating\">https:\/\/schema.org\/Rating<\/a>\" class=\"<a>rating<\/a>\" title=\"<a>4<\/a>\"&gt;\n      &lt;span itemprop=\"<a>ratingValue<\/a>\"&gt;4&lt;\/span&gt;\n      5\n    &lt;\/abbr&gt;\n    &lt;span itemprop=\"<a>reviewBody<\/a>\"&gt;This does a fantastic job of presenting the foreign culture of ... !&lt;\/span&gt;\n    &lt;meta itemprop=\"<a>datePublished<\/a>\" content=\"<a>2007-08-01<\/a>\"&gt;\n    &lt;span itemprop=\"<a>author<\/a>\" itemscope itemtype=\"<a href=\"http:\/\/schema.org\/Person\">https:\/\/schema.org\/Person<\/a>\"&gt;\n      &lt;meta itemprop=\"<a>name<\/a>\" content=\"<a>skierpage<\/a>\"&gt;\n      &lt;meta itemprop=\"<a>sameAs<\/a>\" content=\"<a href=\"https:\/\/www.skierpage.com\/about\/\">https:\/\/www.skierpage.com\/about\/<\/a>\"&gt;\n    &lt;\/span&gt;\n  &lt;\/p&gt;\n&lt;\/div&gt;<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The problem is, if I copy and paste this complicated HTML into WordPress&#8217;s post editor, it throws away much of the HTML markup, for example all the <code>&lt;meta&gt;<\/code> tags for information I don&#8217;t want to display, like <code>&lt;meta itemprop=\"datePublished\" content=\"2007-08-01\"&gt;<\/code>. There are any number of dubious plug-ins to WordPress that support parts of schema.org schemas and want money for a professional version from desperate non-technical web site owners who see their traffic dropping and will clutch at straws hoping to appear higher in Google search results, but I don&#8217;t understand what these plug-ins do or don&#8217;t do.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Another representation for this structured data is JSON-LD, a completely separate representation of the semantic information that you stick in your web page and the reader never sees it; see <a href=\"https:\/\/moz.com\/blog\/json-ld-for-beginners\">A Guide to JSON-LD for Beginners<\/a>. So maybe just sticking in a block of JSON-LD will work better (a guide to supporting it in WordPress is in section &#8220;Implementing Structured Data Using JSON-LD&#8221; in <a href=\"https:\/\/torquemag.io\/2016\/12\/schema-markup-wordpress\/\">schema article at torquemag.io<\/a>). Hmmm&#8230;, instead of copying and pasting twice, can I put this inside WordPress myself? Maybe try <a href=\"https:\/\/wordpress.org\/plugins\/wp-structuring-markup\/#description\">Markup (JSON-LD) Structure in schema.org<\/a> plug-in for WordPress? <a href=\"https:\/\/wpengine.com\/resources\/schema-wordpress\/\">wpengine article<\/a> has JSON-LD generators, but they&#8217;re not much good:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"https:\/\/webcode.tools\/\">Webcode.tools<\/a> has a<a href=\"https:\/\/webcode.tools\/json-ld-generator\"> comprehensive generator tool<\/a> but its review type is too generic<\/li><li><a href=\"https:\/\/microdatagenerator.org\/\">Microdatagenerator.org<\/a> has a<a href=\"https:\/\/microdatagenerator.org\/localbusiness-microdata-generator\/\"> markup generator tool<\/a> but it has nothing for reviews.<\/li><li><a href=\"https:\/\/hallanalysis.com\/\">Hall Analysis<\/a> has created a<a href=\"https:\/\/hallanalysis.com\/json-ld-generator\/\"> step-by-step tool<\/a> but it has nothing for reviews.<\/li><\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"tracking-data\">Tracking data<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The problem with JSON-LD is I have to put the same information into the web page twice, first as HTML to display to human readers, and then again in this invisible block of data. Instead I could use Handlebars or something to spit out both the block of JSON and the HTML. A spreadsheet may be best to track most of this information. It sucks for entering formatted text, but probably OK just for a pithy two-sentence review. Let&#8217;s try it!<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"generated-html\">Generated HTML<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Each book review in the spreadsheet should generate both the JSON-LD that web crawlers should read, and a human-readable book review. In the latter, I want things to link to something useful.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Author ISBN should probably link it to <a href=\"https:\/\/en.wikipedia.org\/wiki\/Special:BookSources\/\">https:\/\/en.wikipedia.org\/wiki\/Special:BookSources\/0060932902{ISBN}<\/a>. Or I could accept that Jeff Bezos owns us and have it link to Amazon&#8217;s ASIN? Wikipedia&#8217;s Special:BookSources above creates a query https:\/\/www.amazon.com\/s?k=0060932902, note how the dashes are removed in the query otherwise it doesn&#8217;t work. Spam-filled https:\/\/kindlepreneur.com\/amazon-search-url-isbn-ref\/ says you can use a 10-digit ISBN in place of ASIN, e.g. https:\/\/www.amazon.com\/dp\/0060932902, but you still have to remove the dashes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For the cover, sometimes you can link to a cover image on English Wikipedia or Wikimedia Commons. You can mess around with an Amazon image URL; for some reason images on ecx.images-amazon.com <a href=\"http:\/\/ecx.images-amazon.com\/images\/I\/51Gvu3UlqGL.jpg\">can&#8217;t be accessed using https<\/a>, Firefox complains about &#8220;SSL_ERROR_BAD_CERT_DOMAIN.&#8221; The Internet Archive runs (hosts?) the <a href=\"https:\/\/openlibrary.org\/dev\/docs\/api\/covers\">Open Library Covers Repository<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Other items in the review, like the author name and book title, should link to Wikipedia pages if available. There&#8217;s no easy way to know that Ian McDonald&#8217;s English Wikipedia page is at https:\/\/en.wikipedia.org\/wiki\/Ian_McDonald_(British_author), so the spreadsheet needs to have columns for Author URL and Book URL. (The alternative would be to store the Wiki<em>data<\/em> &#8216;Q&#8217; numbers for each of these and work backwards from the wikidata info to the English Wikipedia pages, if any, for them.)<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"coding-it\">Coding it<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Uh, scripting&#8230; Python? I quickly found a library <a href=\"https:\/\/pypi.org\/project\/pyexcel-ods\/\">pyexcel-ods<\/a> to read a spreadsheet, and everyone uses seems <a href=\"https:\/\/jinja.palletsprojects.com\/en\/2.11.x\/\">jinja2<\/a> for HTML templating in Python. Adding these libraries mean dealing with all the ways to manage the Python libraries in a project; I have used <code>pip<\/code> and <code>virtualenv<\/code> in the past, but now teh hotness is <code>pipenv<\/code>, so install that and then add <code>pyexcel-ods<\/code> and <code>jinja2<\/code>. I&#8217;m rocking! In two hours I&#8217;ve read a line of my book reviews spreadsheet and generated some HTML<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then I upgraded to Fedora 32, and nothing works because its Python is now at version 3.8, so I have to coerce pipenv to rebuild everything. Guessing what to do, I run <code>pipenv check<\/code> and it tells me &#8220;In order to get an API Key you need a monthly subscription on <a href=\"https:\/\/pyup.io\">pyup.io<\/a>, starting at $14.99&#8243; Guess I won&#8217;t run that command then.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"html-generation\">HTML generation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For now my script plus template just generates a big HTML file of every book review in the spreadsheet. I&#8217;ll want to create blog posts about related books, such as &#8220;Interesting science&#8221;, which means selecting a few chunks from the generated HTML and pasting them into WordPress. WordPress accepts HTML but <em>really wants<\/em> you to use its Gutenberg WYSIWYG blog post editor. Fortunately, it seems I can choose Gutenberg&#8217;s &#8220;Custom HTML&#8221; block and paste in all my generated HTML, including <code>&lt;script&gt;<\/code> tags containing JSON-LD. Finally, something easy! Part of me wants to make the HTML resemble Gutenberg&#8217;s blocks for WYSIWYG editing, but in theory I should go back into the spreadsheet to fix any errors.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"designing-the-json-ld\">Designing the JSON-LD<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">JSON (JavaScript Object Notation) is a simple file and data format to represent data. JSON-LD takes this and makes it slightly more complicated to represent Linked Data: on this Web page a <em>person<\/em> authored this <em>review<\/em> of a <em>book<\/em> which has its own author, another <em>person<\/em>(s). The details quickly degenerate into semantic triples, contexts, more three-letter acronyms like RDF, etc.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"a-person\">A person, a name, a friend-of-a-friend<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Schema.org has fairly simple examples of JSON-LD for a review, but they leave it unclear if just writing <code>\"author\": \"skierpage\"<\/code> is enough for computers to figure out that the person writing the review is the person who runs this web site.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>update 2021-11<\/em> Google Search Console has started objecting to a plain <code>author\": \"skierpage\"<\/code>, now complaining:<br>   &#8216;Review snippets issues detected &#8230; Invalid object type for field &#8220;author&#8221;&#8216;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So it seems I must go to more complicated nested structure for myself:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\"author\": &#91;\n  {\n    \"@type\": \"Person\",\n    \"name\": \"skierpage\",\n    <em>\/\/ Somehow point to some of the existing info about me all over my site!<\/em>\n  }\n],<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Identifying a person on the web has been a concern for almost two decades. I cobbled together a <a href=\"\/people\/skierpage\/foaf.rdf\">FOAF (friend-of-a-friend) record for myself<\/a> and my public key in 2012 back when it seemed you could tell the world &#8220;I&#8217;m skierpage dammit, use my web site to prove it&#8221; using <a href=\"\/openid\/\">OpenID<\/a>, Persona, etc. to authenticate yourself on every web site instead of having to screw around with a hundred usernames and logins. Like most other initiatives going up against incumbent all-powerful social networks, it all mostly died, so people were forced to give up and login using Facebook <s>or Google+<\/s> to provide Mark Fuckerberg with yet more information about your unrelated activities for no good reason. Should I update to <a href=\"https:\/\/microformats.org\/wiki\/hcard\">hReview hCard<\/a>? A <a href=\"https:\/\/en.wikipedia.org\/wiki\/WebID\">WebID<\/a>? An instance on a pod running on Tim Berners-Lee&#8217;s dream of a better Web, &#8220;Solid&#8221;? Arghhh.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It&#8217;s safest to use the <a href=\"https:\/\/schema.org\/Person\">Person<\/a> schema information from the same schema.org that defines a Review. But I don&#8217;t want to have to duplicate my Person info as the author info in each review on each page. I should be able to point the author in all my reviews to a single Person chunk of data, which I&#8217;ve created at <a href=\"https:\/\/www.skierpage.com\/people\/skierpage\/\">https:\/\/www.skierpage.com\/people\/skierpage\/<\/a>. Tantalizingly, the Review schema says &#8220;Please note that author is special in that HTML 5 provides a special mechanism for indicating authorship via the rel tag. That is equivalent to this and may be used interchangeably.&#8221; But in my experiments with <a href=\"https:\/\/search.google.com\/test\/rich-results\">Google&#8217;s Rich Results Test, Google<\/a> ignores an <code>&lt;a rel=\"author\"&gt;<\/code> link to this chunk in the HTML of the page, and complains that author is missing from the Review. So it seems I must put all of<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\"author\": {\n  \"@type\": \"Person\",\n  \"name\": \"skierpage\",\n  \"@id\": \"https:\/\/www.skierpage.com\/people\/skierpage\/#person\",\n  \"url\": \"https:\/\/www.skierpage.com\/people\/skierpage\/\"\n},<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">into each Review. I can&#8217;t even leave out <code>\"name\"<\/code>, and it&#8217;s hella confusing whether the URL should be the web page or an identifier for me with a dummy <code>#person<\/code> hash fragment on the end, or whether I should include both <code>\"url\"<\/code> and <code>\"@id\"<\/code>. The page is not the thing it describes. It seems even if the page with a Review passes Google&#8217;s test, Google doesn&#8217;t bother looking up my Person info anyway!<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"a-graph-of-reviews-a-person-with-lots-of-reviews-a-list-of-products-with-reviews\">A graph of reviews, a person with lots of reviews, a list of products with reviews ??<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">To have multiple book reviews on a web page, you can output a separate JSON-LD <code>&lt;script&gt;<\/code> block along with each review&#8217;s chunk of HTML. This results in a lot of duplication of the reviewer (me) in the page. There are <em>much fancier<\/em> ways to organize this: you can output a single JSON-LD block containing all the reviews by putting them into <a href=\"https:\/\/www.w3.org\/TR\/2014\/REC-json-ld-20140116\/#named-graphs\">top-level &#8220;@graph&#8221; object<\/a> which isn&#8217;t mentioned on schema.org but is part of JSON-LD (or maybe use schema.org&#8217;s <a href=\"https:\/\/schema.org\/ItemList\">@itemList<\/a>&#8230; when you&#8217;re designing a set of linked objects there&#8217;s always more than one way to do it). What&#8217;s unclear is if the JSON-LD should have a graph of books, each with a single review, or a graph of reviews, each of a single <code>itemReviewed<\/code> that&#8217;s a book: <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n\t\"@context\": \"http:\/\/schema.org\/\",\n\t\"@graph\": &#91;{\n\t\t\"@type\": \"Review\",\n\t\t\"author\": \"skierpage\",\n\t\t\"datePublished\": \"2011-04-01\",\n\t\t\"reviewBody\": \"The book has a nice cover.\",\n\t\t\"itemReviewed\": {\n\t\t\t\"@type\": \"Book\",\n\t\t\t\"name\": \"River of Gods\",\n\t\t\t\"isbn\": \"03-5091234-0344\",\n\t\t\t\"author\": \"Ian McDonald\"\n\t\t},\n\t\t\"reviewRating\": {\n\t\t\t\"@type\": \"Rating\",\n\t\t\t\"ratingValue\": 4,\n\t\t\t\"worstRating\": 1,\n\t\t\t\"bestRating\": 5\n\t\t}\n\t},\n\t{\n\t\t... another review\n\t}]\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/search.google.com\/structured-data\/testing-tool\/u\/0\/\">Google&#8217;s Rich Results Test<\/a> doesn&#8217;t like the above, it complains the review is missing a <code>description<\/code>, <code>publisher<\/code>, and <code>url<\/code>. Isn&#8217;t this all obvious from the web page?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Maybe I don&#8217;t need <code>author<\/code>, <a href=\"https:\/\/schema.org\/Review\">https:\/\/schema.org\/Review<\/a> says &#8220;Please note that <a href=\"https:\/\/schema.org\/author\">author<\/a> is special in that HTML 5 provides a special mechanism for indicating authorship via the <code>rel<\/code> tag. That is equivalent to this and may be used interchangeably.&#8221; However, WordPress doesn&#8217;t add <code>rel=\"author\"<\/code> to its Posted by skierpage link.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"actually-writing-out-the-json-ld\">Actually writing out the JSON-LD<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">There is a fancy <a href=\"https:\/\/github.com\/digitalbazaar\/pyld\"><code>pyld<\/code> Python module<\/a> that outputs JSON-LD but I&#8217;m not clear what it does over simply printing <code>json.dumps(reviewJSON)<\/code>. So I just build up reviewJSON as a Python dictionary object:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    reviewJSON = {\n      \"@context\": \"https:\/\/schema.org\",\n      \"@type\": \"Book\",\n      \"author\": bookDict&#91;\"Author\"],\n      \"isbn\": bookDict&#91;\"ISBN\"],                                 \n      \"name\": bookDict&#91;\"Name\"],\n      \"review\": {\n        \"@type\": \"Review\",\n        \"author\": \"skierpage\", ## TODO: can this be derived\/inferred from the page?\n        \"datePublished\": TODAY, ## TODO: can this be derived\/inferred from the page?\n        ...<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/json-ld.org\/playground\/\">https:\/\/json-ld.org\/playground\/<\/a> lets you test the generated markup.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Summary: in early 2021 I got this pretty much working! E.g. View &gt; Source of <a href=\"\/blog\/2021\/02\/william-gibson-flashes-of-excellence\/\">William Gibson flashes of excellence<\/a>. I don&#8217;t think I&#8217;ll bother going back to re-publish older book reviews I hand-edited using hReview markup, e.g. <a href=\"\/blog\/2015\/07\/books-bad-sf\/\">bad SF<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I have a substantial pile of books I&#8217;ve read that will injure me in an earthquake. I ought to write perspicacious pithy reviews of them. I could write them on Amazon, but why should Amazon own and profit from my &hellip; <a href=\"https:\/\/www.skierpage.com\/blog\/2020\/05\/web-book-reviews-yet-again\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_crdt_document":"","footnotes":""},"categories":[5,25,18],"tags":[],"class_list":["post-1010","post","type-post","status-publish","format-standard","hentry","category-books","category-semantic-web-web","category-software"],"_links":{"self":[{"href":"https:\/\/www.skierpage.com\/blog\/wp-json\/wp\/v2\/posts\/1010","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.skierpage.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.skierpage.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.skierpage.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.skierpage.com\/blog\/wp-json\/wp\/v2\/comments?post=1010"}],"version-history":[{"count":9,"href":"https:\/\/www.skierpage.com\/blog\/wp-json\/wp\/v2\/posts\/1010\/revisions"}],"predecessor-version":[{"id":1295,"href":"https:\/\/www.skierpage.com\/blog\/wp-json\/wp\/v2\/posts\/1010\/revisions\/1295"}],"wp:attachment":[{"href":"https:\/\/www.skierpage.com\/blog\/wp-json\/wp\/v2\/media?parent=1010"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.skierpage.com\/blog\/wp-json\/wp\/v2\/categories?post=1010"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.skierpage.com\/blog\/wp-json\/wp\/v2\/tags?post=1010"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}