<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[The Daily Signal]]></title><description><![CDATA[Straightforward solutions to common software development problems.]]></description><link>https://lowrey.me/</link><image><url>https://lowrey.me/favicon.png</url><title>The Daily Signal</title><link>https://lowrey.me/</link></image><generator>Ghost 4.48</generator><lastBuildDate>Fri, 16 Jan 2026 04:06:39 GMT</lastBuildDate><atom:link href="https://lowrey.me/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Random Civilization VI Quote Viewer in React]]></title><description><![CDATA[<p>I recently wrote a better viewer for an <a href="https://lowrey.me/civilization-vi-quotes/">earlier post</a> I have on this blog. Now you can simulate discovering new technology without ever loading up Civiliation VI!</p><p>As a big fan of <em>Civilization VI</em>, I recently decided to create a program that displays a random quote from the game,</p>]]></description><link>https://lowrey.me/random-civilization-vi-quote-in-react/</link><guid isPermaLink="false">671bff25170611ca78db4956</guid><dc:creator><![CDATA[Bret Lowrey]]></dc:creator><pubDate>Fri, 25 Oct 2024 20:32:38 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1653423383200-5af8e48d9867?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDI4fHxjaXZpbGl6YXRpb258ZW58MHx8fHwxNzI5ODg3OTkxfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1653423383200-5af8e48d9867?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDI4fHxjaXZpbGl6YXRpb258ZW58MHx8fHwxNzI5ODg3OTkxfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Random Civilization VI Quote Viewer in React"><p>I recently wrote a better viewer for an <a href="https://lowrey.me/civilization-vi-quotes/">earlier post</a> I have on this blog. Now you can simulate discovering new technology without ever loading up Civiliation VI!</p><p>As a big fan of <em>Civilization VI</em>, I recently decided to create a program that displays a random quote from the game, inspired by the iconic messages that accompany each new technology discovery. This viewer builds on an <a href="https://lowrey.me/civilization-vi-quotes/">earlier post</a> I wrote here, but now it&#x2019;s more interactive and immersive, making it feel like you&apos;re advancing through the tech tree without even having to open the game.</p><!--kg-card-begin: html--><iframe src="https://codesandbox.io/embed/r98p3z?view=preview&amp;module=%2Fsrc%2FApp.tsx&amp;hidenavigation=1" style="width:100%; height: 500px; border:0; border-radius: 4px; overflow:hidden;" title="civ6 random" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Kanji 777 Trainer]]></title><description><![CDATA[<p>Learning kanji can feel overwhelming, but here&#x2019;s something interesting: with just 777 carefully selected kanji, you can understand over 90% of the characters you&apos;ll encounter in everyday Japanese. These kanji, originally adopted from mainland Asia over centuries, have woven themselves into the very fabric of the</p>]]></description><link>https://lowrey.me/kanji-777-trainer/</link><guid isPermaLink="false">671563d8170611ca78db4935</guid><dc:creator><![CDATA[Bret Lowrey]]></dc:creator><pubDate>Sun, 20 Oct 2024 20:23:29 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1486303954368-398fea0e72cd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGthbmppfGVufDB8fHx8MTcyOTQ1NTkzNXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1486303954368-398fea0e72cd?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGthbmppfGVufDB8fHx8MTcyOTQ1NTkzNXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="Kanji 777 Trainer"><p>Learning kanji can feel overwhelming, but here&#x2019;s something interesting: with just 777 carefully selected kanji, you can understand over 90% of the characters you&apos;ll encounter in everyday Japanese. These kanji, originally adopted from mainland Asia over centuries, have woven themselves into the very fabric of the Japanese language, forming the backbone of countless verbs and nouns.</p><p>If you aim for native-level proficiency, mastering kanji isn&#x2019;t optional&#x2014;it&#x2019;s essential. But rather than slogging through 2,400 random characters, why not focus on the most common 800 and get the bulk of the language covered? This is the thinking behind the kanji trainer I built. It&apos;s designed to help you prioritize your learning intelligently, much like Japanese Complete, which teaches stroke order, meaning, and mnemonic breakdowns. My tool serves a similar purpose, offering a minimalist, straightforward approach to mastering kanji efficiently.</p><h2 id="the-design"><br>The Design</h2><p>The page&apos;s design is deliberately minimal. Each kanji is presented in large font, demanding attention. The learner must first engage with the character alone before clicking to reveal its meanings, hiragana, and romanji. This design echoes a principle found in modernist art: let the essential object stand, unadorned, in its own right. In this way, the kanji remains the focal point until the learner decides to dig deeper.</p><!--kg-card-begin: html--><iframe width="100%" height="800" src="//jsfiddle.net/lowrey/5sxvz4cb/embedded/" frameborder="0" loading="lazy" allowtransparency="true" allowfullscreen="true"></iframe><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Use Leaflet.js to create custom maps]]></title><description><![CDATA[<p>While looking for a solution to display a map embedded into a web page, I came across several options. My initial idea was to use something like Google Maps or Bing Maps. Both wanted me to sign up for a developer account to get access to their APIs. Additionally, they</p>]]></description><link>https://lowrey.me/use-leaflet-js-to-create-custom-maps/</link><guid isPermaLink="false">62784d469397157708f1ca91</guid><dc:creator><![CDATA[Bret Lowrey]]></dc:creator><pubDate>Thu, 03 Mar 2022 05:57:05 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1619468129361-605ebea04b44?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDR8fG1hcHxlbnwwfHx8fDE2NDYyODcwMTY&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1619468129361-605ebea04b44?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDR8fG1hcHxlbnwwfHx8fDE2NDYyODcwMTY&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Use Leaflet.js to create custom maps"><p>While looking for a solution to display a map embedded into a web page, I came across several options. My initial idea was to use something like Google Maps or Bing Maps. Both wanted me to sign up for a developer account to get access to their APIs. Additionally, they often impose extreme rate limiting or superimpose intrusive watermarks unless you upgrade to a paid account.</p><p>OpenStreetMap seemed like a great alternative. Over the past few years, its maps have greatly increased in quality. Its open licensing and public tileset make it easy to include in any project. I initially struggled to find any quick examples of how to utilize it in browser with Javascript, but I came across <a href="https://web.archive.org/web/20200930043512/https://github.com/Leaflet/Leaflet">Leaflet.js</a> and it seemed like a great fit. It is open source, lightweight, and mobile ready.</p><p>Below is a quick example of what I ended up using it for. &#xA0;You initialize the map with a location ([38, -97] is the geographic center of the US), zoom level (4 seems to fit the entire US), and a tileset (OpenStreetMap). All that is needed to add a marker is a to call the <code>marker</code> function with decimal GPS coordinates.</p><figure class="kg-card kg-embed-card"><iframe src="https://web.archive.org/web/20200930043512/https://jsfiddle.net/Lkyghf7w/embedded/?username=lowrey" id="JSFEMB_18535" width="100%" height="756.375" frameborder="0" sandbox="allow-modals allow-forms allow-same-origin allow-scripts allow-popups allow-top-navigation-by-user-activation allow-downloads" allow="autoplay &apos;self&apos;; fullscreen &apos;self&apos;" style="box-sizing: inherit; margin: 0px auto !important; padding: 0px; border: 0px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-numeric: inherit; font-variant-east-asian: inherit; font-weight: 400; font-stretch: inherit; line-height: inherit; font-family: Georgia, serif; font-size: 20px; vertical-align: middle; color: rgb(48, 58, 62); letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"></iframe></figure>]]></content:encoded></item><item><title><![CDATA[Python Cheat Sheet]]></title><description><![CDATA[<p>I&apos;ve been using Python after a dry spell of using other languages. I went through the basic mechanics of the language and tried to use a wide variety of different techniques to cover most of the usage. Here&apos;s what I came up with.</p><pre><code class="language-python">from random import</code></pre>]]></description><link>https://lowrey.me/python-cheat-sheet/</link><guid isPermaLink="false">62784d469397157708f1ca90</guid><dc:creator><![CDATA[Bret Lowrey]]></dc:creator><pubDate>Thu, 03 Mar 2022 05:56:06 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1538439907460-1596cafd4eff?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDR8fHB5dGhvbnxlbnwwfHx8fDE2NDYyODY5NTQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1538439907460-1596cafd4eff?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDR8fHB5dGhvbnxlbnwwfHx8fDE2NDYyODY5NTQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Python Cheat Sheet"><p>I&apos;ve been using Python after a dry spell of using other languages. I went through the basic mechanics of the language and tried to use a wide variety of different techniques to cover most of the usage. Here&apos;s what I came up with.</p><pre><code class="language-python">from random import random, randint, choice, shuffle
from math import log, log10, log2
from itertools import count, repeat, cycle, chain, islice
import re


print(&apos;lists&apos;)
l = [1, 2, 4, 3, 5]
print(l)
l.append(6)
l.extend([8, 7])
print(l)
print(sorted(l, reverse=True))
print(reversed(l))
for i, x in enumerate(l):
    print(i, x)
print()

print(&apos;dicts&apos;)
d = {&apos;a&apos;: 1, &apos;b&apos;: 2, &apos;c&apos;: 3}
print(d, d.keys(), d.values())
print(d.get(&apos;a&apos;), d[&apos;b&apos;], d.get(&apos;z&apos;))
d.setdefault(0)
print(d.get(&apos;z&apos;))
d.update({&apos;d&apos;: 4})
print(d)
print(dict(zip(range(9), l)))
print()

print(&apos;sets&apos;)
s = set([1, 1, 1, 2])
s.add(3)
s.remove(3)
s.discard(55)
print(s, s.union([4]))
print(s.difference([1]))
print(s.intersection([1]))
print()

print(&apos;regex&apos;)
m = re.search(r&apos;(\d{4})-(\d{2})-(\d{2})&apos;, &apos;2016-01-01&apos;)
print(m.groups())
res = &quot;1a2b3c&quot;
newres = re.sub(r&apos;[a-z]&apos;, &apos; &apos;, res)
print(newres)
print()

print(&apos;itertools&apos;)
print(list(islice(count(start=0, step=1), 2)))
print(list(repeat(5, 3)))
print(list(chain([1, 2, 3], [4, 5, 6])))
print(list(islice(cycle([1, 2, 3]), 7)))


def count2(start, step):
    while True:
        yield start
        start += step


print(next(count2(6, 2)))
print()

print(&apos;type&apos;)
print(type(5), isinstance(5, int))
print()

print(&apos;string&apos;)
s = &apos; asdf,abc#&apos;
print(s.strip(), s.strip(&apos;#&apos;))
print(s.split(&apos;,&apos;), &apos;,&apos;.join(str(x) for x in [1, 2, 3]))
print(s.find(&apos;,&apos;), s.find(&apos;x&apos;))
print(&apos;{}: ${:3,}&apos;.format(&apos;Cost&apos;, 1234))
print()

print(&apos;stdin&apos;)
# if __name__ == &apos;__main__&apos;:
# jptr = open(os.environ[&apos;OUTPUT_PATH&apos;], &apos;w&apos;)
# s = input()
# result = isValid(s)
# fptr.write(result + &apos;\n&apos;)
# fptr.close()
print()

print(&apos;numbers&apos;)
s = &apos;123&apos;
i = int(s)
print(pow(i, 2), abs(-i), round(i + .94423, 3))
print(log2(i))
r_float = random()
r_int = randint(0, 10)
arr = [1, 2, 3]
r_el = choice(arr)
shuffle(arr)
print(r_float, r_int, arr, r_el)
print(~100, 101 &amp; 9, 101 | 9)
print()

print(&apos;date&apos;)
from datetime import date, time, datetime, timedelta
now = datetime.utcnow()
print(now)
print()

print(&apos;list comprehension&apos;)
l = [i+1 for i in range(5)]                   # [1, 2, ..., 10]
s  = {i for i in range(10) if i &gt;3}            # {6, 7, 8, 9}
iter = (i+5 for i in range(5))                   # (5, 6, ..., 14)
d = {i: i*2 for i in range(5)}                # {0: 0, 1: 2, ..., 9: 18}
from pprint import pprint
print(l, s, next(iter), d, any(x % 2 == 0 for x in iter))
pprint(d, width=10)
print()

print(&apos;lambda and enum&apos;)
from functools import reduce
iter = map(lambda x: x + 1, range(10))            # (1, 2, ..., 10)
iter2 = filter(lambda x: x &gt; 5, range(10))         # (6, 7, 8, 9)
o  = reduce(lambda out, x: out + x, range(10))  # 45
print(next(iter), list(iter2), o)
from enum import Enum
Direction = Enum(&apos;Direction&apos;, &apos;n e s w&apos;)
print(Direction, Direction.n)
print()

print(&apos;try catch&apos;)
class CustomEx(Exception):
    pass

try:
    print(&apos;try&apos;)
    raise CustomEx(&apos;msg&apos;)
except CustomEx as e:
    print(&apos;except&apos;, e)
finally:
    print(&apos;finally&apos;)
print()</code></pre>]]></content:encoded></item><item><title><![CDATA[Convert CSV to KML/KMZ using Google Maps]]></title><description><![CDATA[<p>In <a href="https://web.archive.org/web/20200930043430/https://lowrey.me/scraping-wikipedia-with-nightmare/">my last post</a>, I was bulk gathering GPS coordinates from the internet. After I had them, I still needed a method to display them. Luckily, <a href="https://web.archive.org/web/20200930043430/https://www.google.com/maps/d/">Google My Maps</a> offers a versatile import function. Every new layer can bulk import data from a variety of data formats.</p><figure class="kg-card kg-image-card"><img src="https://web.archive.org/web/20200930043430im_/https://lowrey.me/content/images/2019/07/image-1.png" class="kg-image" alt loading="lazy"></figure><p>On Wikipedia, coordinates</p>]]></description><link>https://lowrey.me/convert-csv-to-kml-kmz-using-google-maps/</link><guid isPermaLink="false">62784d469397157708f1ca8f</guid><dc:creator><![CDATA[Bret Lowrey]]></dc:creator><pubDate>Thu, 03 Mar 2022 05:55:17 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1597020207827-529e8637f129?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDExfHxmb3Jlc3QlMjBnaG9zdCUyMHRvd258ZW58MHx8fHwxNjUyODQ5Njgw&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1597020207827-529e8637f129?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDExfHxmb3Jlc3QlMjBnaG9zdCUyMHRvd258ZW58MHx8fHwxNjUyODQ5Njgw&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Convert CSV to KML/KMZ using Google Maps"><p>In <a href="https://web.archive.org/web/20200930043430/https://lowrey.me/scraping-wikipedia-with-nightmare/">my last post</a>, I was bulk gathering GPS coordinates from the internet. After I had them, I still needed a method to display them. Luckily, <a href="https://web.archive.org/web/20200930043430/https://www.google.com/maps/d/">Google My Maps</a> offers a versatile import function. Every new layer can bulk import data from a variety of data formats.</p><figure class="kg-card kg-image-card"><img src="https://web.archive.org/web/20200930043430im_/https://lowrey.me/content/images/2019/07/image-1.png" class="kg-image" alt="Convert CSV to KML/KMZ using Google Maps" loading="lazy"></figure><p>On Wikipedia, coordinates are displayed in the degree-minute-second format. Since Google Maps seems to favorite the degree-decimal format, I was uncertain if importing would convert them to the correct location.</p><figure class="kg-card kg-code-card"><pre><code class="language-csv">Name,Location for Google
&quot;Alpha, California&quot;,&quot;39&#xB0;19&#x2032;50&#x2033;N,120&#xB0;46&#x2032;53&#x2033;W&quot;
&quot;Badger Hill, California&quot;,&quot;39&#xB0;12&#x2032;58&#x2033;N,121&#xB0;02&#x2032;58&#x2033;W&quot;
&quot;Baltimore Town, California&quot;,&quot;39&#xB0;23&#x2032;12&#x2033;N,120&#xB0;31&#x2032;51&#x2033;W&quot;</code></pre><figcaption>Google Maps Import CSV Sample</figcaption></figure><p>The import worked without any issues in with this format. &#xA0;You can import CSV, XLSX, KML or GPX with Google Maps. Google Maps will prompt you for the meaning of each of these fields after you import the CSV. It will even ask you for the format of the coordinates (latitude,longitude vs longitude,latitude).</p><figure class="kg-card kg-image-card"><img src="https://web.archive.org/web/20200930043430im_/https://lowrey.me/content/images/2019/07/image-2.png" class="kg-image" alt="Convert CSV to KML/KMZ using Google Maps" loading="lazy"></figure><p>In order to convert this location data into KML, all I needed to do was export it. The export function can be found in the options for the entire map. You can choose which layers to include in the dialog.</p><figure class="kg-card kg-image-card"><img src="https://web.archive.org/web/20200930043430im_/https://lowrey.me/content/images/2019/07/image-3.png" class="kg-image" alt="Convert CSV to KML/KMZ using Google Maps" loading="lazy"></figure><p>KML is an XML based format. Once you export the map, you can open it up in any text editor to see the data it produced.</p><pre><code class="language-xml">&lt;Placemark&gt;
    &lt;name&gt;Alpha, California&lt;/name&gt;
    &lt;description&gt;39&#xB0;19&#x2032;50&#x2033;N,120&#xB0;46&#x2032;53&#x2033;W&lt;/description&gt;
    &lt;styleUrl&gt;#icon-seq2-0-0-0288D1-labelson&lt;/styleUrl&gt;
    &lt;Point&gt;
    &lt;coordinates&gt;
    -120.781388888889,39.3305555555556,0
    &lt;/coordinates&gt;
    &lt;/Point&gt;
&lt;/Placemark&gt;</code></pre><p>If you want to try it out for yourself, you can use the same map I referenced in this article: <a href="https://web.archive.org/web/20200930043430/https://drive.google.com/open?id=1T7WAGwh1oEimHgNUJfdS0BUXBR299S6t&amp;usp=sharing">Nevada County Ghost Towns</a></p>]]></content:encoded></item><item><title><![CDATA[Scraping Wikipedia with NightmareJS]]></title><description><![CDATA[<p>I&apos;ve written a lot of webscrapers in the past. Each approach I&apos;ve used has its benefits and drawbacks. If I need to be able to do anything more complicated than extract some text from an HTML document, <a href="https://web.archive.org/web/20200930043302/https://github.com/segmentio/nightmare">Nightmare</a> has been my favorite way to do it.</p>]]></description><link>https://lowrey.me/scraping-wikipedia-with-nightmarejs/</link><guid isPermaLink="false">62784d469397157708f1ca8e</guid><dc:creator><![CDATA[Bret Lowrey]]></dc:creator><pubDate>Thu, 03 Mar 2022 05:54:01 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1565462905102-140e712045aa?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fHdpa2lwZWRpYXxlbnwwfHx8fDE2NTI4NDk2MTI&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1565462905102-140e712045aa?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fHdpa2lwZWRpYXxlbnwwfHx8fDE2NTI4NDk2MTI&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Scraping Wikipedia with NightmareJS"><p>I&apos;ve written a lot of webscrapers in the past. Each approach I&apos;ve used has its benefits and drawbacks. If I need to be able to do anything more complicated than extract some text from an HTML document, <a href="https://web.archive.org/web/20200930043302/https://github.com/segmentio/nightmare">Nightmare</a> has been my favorite way to do it. It&apos;s easy to use, fast, lends itself to readable code, and comes with a fully featured headless browser with JavaScript.</p><p>Most recently, I was researching a hobby of mine, finding ghosts towns to visit. Wikipedia is a great resource for <a href="https://web.archive.org/web/20200930043302/https://en.wikipedia.org/wiki/Template:Nevada_County,_California">them</a>. It&apos;s a more comprehensive list than I could find anywhere else and many of the towns had GPS coordinates. However, what I really wanted was a map so I could get an idea where each town was in relation to the others. Since I couldn&apos;t find it anywhere, I thought I would map it out myself with a <a href="https://web.archive.org/web/20200930043302/https://www.google.com/maps/d/">custom Google Map</a>. The prospect of copying and pasting each of the 100+ towns into Google Maps seemed daunting.</p><p>So I decided to automate it. First I would need to get the list ghost towns and their respective URLs on Wikipedia. Then I could grab the coordinates from that page and put it into a CSV file that Google Maps could import. Simple enough.</p><p>Installing Nightmare is as easy as running:</p><pre><code>npm install nightmare</code></pre><p>It will install a version of Electron in your node-modules folder. Whenever you initialize your script with Nightmare, it will run your scraping code inside of sandboxed browser.</p><figure class="kg-card kg-code-card"><pre><code class="language-js">const nightmare = require(&apos;nightmare&apos;);

// Run Nightmare in an async function to take advantage of 
// ES7&apos;s await
async function run() {
  // Set large timeout of 150 seconds, default is 30
  const nm = nightmare({
    waitTimeout: 150000,
  });
    
  // Grab the list of towns from the county webpage
  const towns = await nm.goto(`https://en.wikipedia.org/wiki/Template:Nevada_County,_California`)
    .wait(&apos;body&apos;)
    .evaluate(function() {
      const ghostTown = document
        .querySelector(&apos;tr &gt; th &gt; a[title=&quot;Ghost town&quot;]&apos;);
      const row = ghostTown.parentElement.parentElement;
      // get the URL for each town from its link
      const towns = Array.from(row.querySelectorAll(&apos;li &gt; a&apos;))
        .map((el) =&gt; el.href);
      return towns;
    })
    .catch(function(error) {
      console.error(&apos;did not get towns&apos;, error);
    });
    
  // Set a delay of 1 second per page, to play nice with
  // Wikipedia&apos;s rate limiting
  let delay = 1000;
  const res = towns.map(async function(url) {
    // increasing this delay will stagger each promise to 
    // roughly execute 1 second after each other
    delay += 1000;
    const coord = await nm.wait(delay)
      .goto(url)
      .wait(&apos;body&apos;)
      .evaluate(() =&gt; {
        // scrape latitude and longitude data
        const lat = document
          .querySelector(&apos;span[class=&quot;latitude&quot;]&apos;).innerHTML;
        const long = document
          .querySelector(&apos;span[class=&quot;longitude&quot;]&apos;).innerHTML;
        return {lat, long};
      })
      .catch((error) =&gt; {
        console.error(`did not get coords: ${url}, ${error}`);
      });
    return {
      url: url,
      coord: coord,
    };
  });
  const coords = await Promise.all(res);
  // Only display towns that have coordinates
  console.log(coords.filter((c) =&gt; c.coord !== undefined));
  await nm.end();
}

run();
</code></pre><figcaption>ghost-town-scraper.js</figcaption></figure><p>Using Nightmare is a lot like scripting a browser. You initialize it with <code>nightmare()</code> which is a lot like starting up the browser. Visiting new pages can be achieved by calling its <code>.goto(url)</code> function. You can chain any number of <a href="https://web.archive.org/web/20200930043302/https://github.com/segmentio/nightmare#api">these actions</a> and the Election instance will evaluate them sequentially. <code>.evaluate(function)</code> allows you to execute Javascript code within that instance. It gives you everything you&apos;d expect in a browser console, including the <code>document</code> and <code>window</code> object.</p><p>I ran into some issues with Wikipedia&apos;s rate limiting as I was fetch over 100 pages asynchronously. To get around this, I &#xA0;used Nightmare&apos;s <code>.delay(ms)</code> functionality. This will pause the browser for that specified amount of time. I did it before even going to the page to prevent too many sequential page loads.</p><p>Nightmare is uses asynchronous functions at its core. Almost every operation done with it returns a promise. It&apos;s a great use case for ES7 await keyword. Without it, you start to get into some fairly nasty nested function which will make the code a lot less readable.</p><p>As a last note, if you are running this on a Linux machine without X installed like I was, Electron will silently fail. To get around this I installed <a href="https://web.archive.org/web/20200930043302/https://gist.github.com/SantoshSrinivas79/130877925f52c8fb2557">xvfb</a> and ran my script with <code>xvfb-run node ghost-town-scraper.js</code>.</p>]]></content:encoded></item><item><title><![CDATA[Nginx redirects using Lua]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>For years, I&apos;ve been a big fan of DuckDuckGo&apos;s <a href="https://duckduckgo.com/bang">bang</a> feature. I use it any time I have a specific site I want to search. For example, if I know the result I want to get is on Wikipedia, you can simply use <code>!w [search term]</code></p>]]></description><link>https://lowrey.me/nginx-redirects-using-lua/</link><guid isPermaLink="false">62784d469397157708f1ca8c</guid><category><![CDATA[nginx]]></category><category><![CDATA[lua]]></category><dc:creator><![CDATA[Bret Lowrey]]></dc:creator><pubDate>Mon, 16 Jul 2018 22:01:19 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1494519870370-1344df8bbb10?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=f537fb15e89c5bfe4b047d72e45e8ae6" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1494519870370-1344df8bbb10?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=f537fb15e89c5bfe4b047d72e45e8ae6" alt="Nginx redirects using Lua"><p>For years, I&apos;ve been a big fan of DuckDuckGo&apos;s <a href="https://duckduckgo.com/bang">bang</a> feature. I use it any time I have a specific site I want to search. For example, if I know the result I want to get is on Wikipedia, you can simply use <code>!w [search term]</code> rather than having to see a search result page or having to go to Wikipedia and using their search box. You wouldn&apos;t think the extra click matters, but it adds up.</p>
<p>However, with DuckDuckGo, if you do not put a <code>!</code> at the beginning of your search, it will act like any other search engine and show you a page of results.</p>
<p>Google is still the king of getting relevant search results, so I often find myself switching between the two.  So much so, I used <a href="http://duckduckgoog.com">DuckDuckGoog</a> which provides google search results if you do not use the bang feature but redirects you to DuckDuckGo if you do. I&apos;ve found it to be the best of both worlds.</p>
<p>Today, DuckDuckGoog is down. It&apos;s a fairly straightforward hack to check if your search query starts with a <code>!</code>, so I decided to host my own.  I had never done so in the past because it seemed like such a trivial service to host. It hardly seems worth it to load up a PHP script or redirect to a NodeJS page.</p>
<p>I settled on using <a href="https://github.com/openresty/lua-nginx-module">NGINX&apos;s Lua</a> scripting capabilities.  It seemed like the right choice because it gets you as close to the webserver as possible but frees you from the limitations of NGINX&apos;s config language.  It&apos;s available in the <a href="https://packages.debian.org/sid/nginx-extras">nginx-extras</a> package on any debian derived system like Ubuntu or Mint.</p>
<p>You&apos;ll need to place this section of config of whatever host you are serving it from.</p>
<h3 id="nginxconfiguration">NGINX configuration</h3>
<pre><code class="language-nginx">location /search {
&#xA0; include /path/to/duckduckgoog.lua;
}
</code></pre>
<p>Here&apos;s the script I came up with. It needs to be placed in a file the webserver has access to.</p>
<h3 id="lua">Lua</h3>
<pre><code class="language-lua">access_by_lua &quot;
local url = ngx.var.uri
local args = ngx.var.args

function starts(str, start)
   return string.sub(str, 1, string.len(start)) == start
end

function split(str, pat)
   local t = {}  -- NOTE: use {n = 0} in Lua-5.0
   local fpat = &apos;(.-)&apos; .. pat
   local last_end = 1
   local s, e, cap = str:find(fpat, 1)
   while s do
      if s ~= 1 or cap ~= &apos;&apos; then
         table.insert(t,cap)
      end
      last_end = e+1
      s, e, cap = str:find(fpat, last_end)
   end
   if last_end &lt;= #str then
      cap = str:sub(last_end)
      table.insert(t, cap)
   end
   return t
end

local q = split(args, &apos;=&apos;)[2]

if starts(q, &apos;!&apos;) then
    local ddg_url = &apos;https://duckduckgo.com/?q=&apos;
    -- print(ddg_url .. q)
    ngx.redirect(ddg_url .. q, 301)
else
    local goog_url = &apos;https://www.google.com/search?q=&apos;
    -- print(goog_url .. q)
    ngx.redirect(goog_url .. q, 301)
end
&quot;;
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Set content of a frame with HTML string]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I ran into a problem when I was trying to display an HTML page hosted on Github. Since Github serves HTML files as raw text in the content type headers, you can&apos;t just browse to the raw file in a repo and view it. Usually <a href="https://rawgit.com">RawGit</a> works great</p>]]></description><link>https://lowrey.me/set-content-of-a-frame-with-html-string/</link><guid isPermaLink="false">62784d469397157708f1ca8b</guid><category><![CDATA[javascript]]></category><dc:creator><![CDATA[Bret Lowrey]]></dc:creator><pubDate>Sun, 27 May 2018 16:24:29 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1497296690583-da0e2a4ce49a?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=ca95e752fc5f5d292f63536903e63b96" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1497296690583-da0e2a4ce49a?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=ca95e752fc5f5d292f63536903e63b96" alt="Set content of a frame with HTML string"><p>I ran into a problem when I was trying to display an HTML page hosted on Github. Since Github serves HTML files as raw text in the content type headers, you can&apos;t just browse to the raw file in a repo and view it. Usually <a href="https://rawgit.com">RawGit</a> works great as a shim, but I have found unpredictable behavior on larger html files, especially when they are Javascript heavy.</p>
<p>I wanted to embed this content on another webpage and it seemed like frames were a good match for what I wanted to accomplish.  Frames usually take a URL in the src attribute to load there content.  This was a problem because if I gave it the URL to the raw file, it would still honor the content type header and display it as raw text instead of HTML.</p>
<p>I came up with a way of creating an empty I frame and simply setting the content after it is created.  You can do this using the DOM method <code>document.writeln()</code>:</p>
<pre><code class="language-javascript">const setFrame = (el, html) =&gt; {
  if (el.contentDocument) {
    el.document = el.contentDocument;
  }
  if (el.document !== null) {
    el.document.open();
    el.document.writeln(html);
    el.document.close();
  }
};
</code></pre>
<p>Here&apos;s a working example:</p>
<script async src="//jsfiddle.net/lowrey/p6grxa6m/2/embed/"></script><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[UUID Generation in C++11]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>The C++ standard libary has come a long way in the last ten years. It contains a lot of useful functions that make stuff easy that used to be a chore in C++ compared to other languages. However, there is still a gap between what the STL provides compared to</p>]]></description><link>https://lowrey.me/guid-generation-in-c-11/</link><guid isPermaLink="false">62784d469397157708f1ca8a</guid><category><![CDATA[C++]]></category><category><![CDATA[guid]]></category><category><![CDATA[uuid]]></category><dc:creator><![CDATA[Bret Lowrey]]></dc:creator><pubDate>Mon, 21 May 2018 03:56:33 GMT</pubDate><media:content url="https://lowrey.me/content/images/2018/05/Q9gIW9q.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://lowrey.me/content/images/2018/05/Q9gIW9q.png" alt="UUID Generation in C++11"><p>The C++ standard libary has come a long way in the last ten years. It contains a lot of useful functions that make stuff easy that used to be a chore in C++ compared to other languages. However, there is still a gap between what the STL provides compared to other modern languages.</p>
<p>One such case is UUID/GUID creation.  In languages like Python and Java, to make a GUID, all you need to do is import a module and you are good to go. In C++, this will still be relegated to an external framework like the Windows API or QT. There are even some smaller <a href="https://github.com/graeme-hill/crossguid">cross platform</a> libraries specifically targeting this functionality. In most cases, this is still the best place to go. These methods are battle-tested: they&apos;ll be fast and robust.</p>
<p>If you want something more portable that doesn&apos;t rely on any external linking, you&apos;ll have to write it yourself. Luckily, the <a href="https://en.wikipedia.org/wiki/Universally_unique_identifier#Format">UUID format</a> isn&apos;t particularly complex. If you can generate random hex characters, you can make a GUID easily.</p>
<p>The STL in C++11 now includes <code>std::random</code> which makes this a lot easier. Before it, you would still probably need to go to an external library to get random values. In combination with std::hex, almost all of the hard parts of this problem can be solved with stuff in the standard library.</p>
<pre><code class="language-cpp">#include &lt;vector&gt;
#include &lt;iostream&gt;
#include &lt;sstream&gt;
#include &lt;random&gt;
#include &lt;climits&gt;
#include &lt;algorithm&gt;
#include &lt;functional&gt;
#include &lt;string&gt;

unsigned char random_char() {
    std::random_device rd;
    std::mt19937 gen(rd()); 
    std::uniform_int_distribution&lt;&gt; dis(0, 255);
    return static_cast&lt;unsigned char&gt;(dis(gen));
}

std::string generate_hex(const unsigned int len) {
    std::stringstream ss;
    for(auto i = 0; i &lt; len; i++) {
        auto rc = random_char();
        std::stringstream hexstream;
        hexstream &lt;&lt; std::hex &lt;&lt; int(rc);
        auto hex = hexstream.str(); 
        ss &lt;&lt; (hex.length() &lt; 2 ? &apos;0&apos; + hex : hex);
    }        
    return ss.str();
}
</code></pre>
<p>These two functions allow you to generate hex random characters of <code>n</code> length.  From there it is as simple calling it for each of the 5 parts of a UUID by their respective lengths. If you can easily access the UUID functionality in another library, use it. It is likely more performant and can take full advantage of the OS APIs.</p>
<p><a href="https://repl.it/@lowrey/C11-GUID-Generation">repl.it link</a></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Formatting JSON without external libraries]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Most programming languages today have great JSON support. If it isn&apos;t built into the language, there is a de facto standard library you can add to your project. Included in these libraries is a formatting function.  For the sake of space, most of these libraries will have your</p>]]></description><link>https://lowrey.me/formatting-json-without-external-libraries/</link><guid isPermaLink="false">62784d469397157708f1ca89</guid><dc:creator><![CDATA[Bret Lowrey]]></dc:creator><pubDate>Thu, 10 May 2018 13:13:43 GMT</pubDate><media:content url="https://lowrey.me/content/images/2018/05/typewriter-1-.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://lowrey.me/content/images/2018/05/typewriter-1-.jpg" alt="Formatting JSON without external libraries"><p>Most programming languages today have great JSON support. If it isn&apos;t built into the language, there is a de facto standard library you can add to your project. Included in these libraries is a formatting function.  For the sake of space, most of these libraries will have your JSON minified by default.  For larger documents, this quickly becomes very unreadable to users.</p>
<p>Because of this, these libraries have a JSON format function. However, it isn&apos;t a terribly hard problem to solve. If all you need to do is format a JSON document that is already a string, many of these libraries would force you to parse the document into memory and then write it out.</p>
<p>Below is a function that formats any valid JSON string. By default, it indents it by two spaces but that is fully parameterized in this function.  It is written in C# because it is one of the few popular languages that relies on a 3rd party library for its JSON functionality.  This could also easily be rewritten in almost any imperative language.</p>
<pre><code class="language-csharp">public static string FormatJson(string json, char indent = &apos; &apos;, int times = 2)
{
	var indentation = 0;
	var quoteCount = 0;
	var indentString = string.Concat(Enumerable.Repeat(indent, times));
	var result = &quot;&quot;;
	foreach (var ch in json) {
		switch (ch) {
			case &apos;&quot;&apos;:
				quoteCount++;
				result += ch;
			break;
			case &apos;,&apos;:
				if (quoteCount % 2 == 0) {
					result += ch + Environment.NewLine + 
                    string.Concat(Enumerable.Repeat(indentString, indentation));
				}
			break;
			case &apos;{&apos;:
			case &apos;[&apos;:
				var openFormatted = string.Concat(Enumerable.Repeat(indentString, ++indentation)) 
                + indent;
				result += string.Format(&quot;{0}{1}{2}&quot;, ch, Environment.NewLine, openFormatted);
			break;
			case &apos;}&apos;:
			case &apos;]&apos;:
				var closeFormatted = string.Concat(Enumerable.Repeat(indentString, --indentation));
				result += string.Format(&quot;{0}{1}{2}&quot;, Environment.NewLine, closeFormatted, ch);
			break;
			default:
				result += ch;
			break;
		}
	}
	return result;
}
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Convert a class of static fields to a dictionary in C#]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I was coding in a C# project when I came across a class that was mapping names to GUIDs. Since GUIDs are objects, you can&apos;t simply put them in a const array or an enum like you can do with simpler data types.</p>
<pre><code class="language-csharp">public static class Guids
{
	public</code></pre>]]></description><link>https://lowrey.me/convert-a-class-of-static-consts-to-a-dictionary-in-c/</link><guid isPermaLink="false">62784d469397157708f1ca88</guid><dc:creator><![CDATA[Bret Lowrey]]></dc:creator><pubDate>Mon, 09 Apr 2018 13:54:21 GMT</pubDate><media:content url="https://lowrey.me/content/images/2018/04/VnCkTy8-1-.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://lowrey.me/content/images/2018/04/VnCkTy8-1-.png" alt="Convert a class of static fields to a dictionary in C#"><p>I was coding in a C# project when I came across a class that was mapping names to GUIDs. Since GUIDs are objects, you can&apos;t simply put them in a const array or an enum like you can do with simpler data types.</p>
<pre><code class="language-csharp">public static class Guids
{
	public static readonly Guid First = new Guid(&quot;F288114A-B29E-40A2-B649-DF87B10B8E7C&quot;);
	public static readonly Guid Second = new Guid(&quot;AFE63E51-A611-426C-8226-4A98A93195DB&quot;);
	public static readonly Guid Third = new Guid(&quot;02CFDDDE-5C0E-4E65-B756-25DB72A08503&quot;);
	public static readonly Guid Fourth = new Guid(&quot;500CDF93-D1D1-4A89-A348-5A1B5265B1FA&quot;);
	public static readonly Guid Fifth = new Guid(&quot;890CD9E4-59B7-4C9B-95BF-9FFC159E169E&quot;);
}
</code></pre>
<p>If I was trying to look up a GUID by name, it would be as easy as accessing the object by the name, but I had a GUID and wanted to get the same for it.</p>
<p>The approach I took was to create a dictionary where the GUID values were the keys and the names were the values. Using the GetFields() method on the type of the object seemed like best way to get the public properties. I could then use LINQ to convert this list of fields to the data structure I needed.</p>
<iframe height="400px" width="600px" src="https://repl.it/@lowrey/StaticObjectFieldsToDictionary?lite=true" scrolling="no" frameborder="no" allowtransparency="true" allowfullscreen="true" sandbox="allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-modals"></iframe><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Use Python's xmltodict to convert XML to JSON]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I found myself recently having to deal with a large and complex XML document. I wanted to use Python&apos;s excellent paradigms like generators and list comprehension to carve up the XML into more meaningful data. However, most libraries that deal with XML parsing provide you with non-standard interfaces</p>]]></description><link>https://lowrey.me/use-pythons-xmltodict-to-convert-xml-to-json/</link><guid isPermaLink="false">62784d469397157708f1ca87</guid><category><![CDATA[python]]></category><dc:creator><![CDATA[Bret Lowrey]]></dc:creator><pubDate>Tue, 03 Apr 2018 00:41:00 GMT</pubDate><media:content url="https://lowrey.me/content/images/2018/04/UlirV2W-1-.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://lowrey.me/content/images/2018/04/UlirV2W-1-.png" alt="Use Python&apos;s xmltodict to convert XML to JSON"><p>I found myself recently having to deal with a large and complex XML document. I wanted to use Python&apos;s excellent paradigms like generators and list comprehension to carve up the XML into more meaningful data. However, most libraries that deal with XML parsing provide you with non-standard interfaces to access XML nodes due to the complexity of the XML spec.</p>
<p>While searching for a solution, I came across the <a href="https://github.com/martinblech/xmltodict">xmltodict</a> library. It turned out to be exactly what I needed. I was surprised at the intuitiveness of the API. Essentially, give it an XML document and it gives you a native Python dictionary with intuitive mappings for XML specific features like attributes and namespaces.  For example, lists in the form of <code>&lt;item&gt;1&lt;/item&gt;&lt;item&gt;2&lt;/item&gt;&lt;item&gt;3&lt;/item&gt;</code> simply become <code>item: [1, 2, 3]</code>. Once it is in Python&apos;s dictionary model, it is trivial to get it to convert to JSON due to their similarity.</p>
<p>Below is a working snippet of a few test cases I came up with to demonstrate its functionality.</p>
<iframe height="400px" width="600px" src="https://repl.it/@lowrey/xmltodict-example?lite=true" scrolling="no" frameborder="no" allowtransparency="true" allowfullscreen="true" sandbox="allow-forms allow-pointer-lock allow-popups allow-same-origin allow-scripts allow-modals"></iframe><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Use System.Xml.XmlDocument to create HTML in C#]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>If you need to create HTML, it is usually a bad idea to just concatenate strings together. It tends to create brittle and invalid documents once there is any level of complexity to it.  You need to manage the position of each tag and when to close it, where to</p>]]></description><link>https://lowrey.me/use-system-xml-xmldocument-to-create-html-in-c/</link><guid isPermaLink="false">62784d469397157708f1ca86</guid><dc:creator><![CDATA[Bret Lowrey]]></dc:creator><pubDate>Tue, 02 Jan 2018 22:48:52 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1461749280684-dccba630e2f6?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=58bba222186b057d9e88ada20ec520a7" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1461749280684-dccba630e2f6?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=58bba222186b057d9e88ada20ec520a7" alt="Use System.Xml.XmlDocument to create HTML in C#"><p>If you need to create HTML, it is usually a bad idea to just concatenate strings together. It tends to create brittle and invalid documents once there is any level of complexity to it.  You need to manage the position of each tag and when to close it, where to insert data, and changing the formatting is a chore. Since HTML is a tree-like data structure, it is much better to model it as such inside your program.</p>
<p>The best option is usually to a bonifide HTML parser/generator.  Most languages either include it in the standard library or has a defacto external library that most users work with.  When I was working with C#, I found that I needed to generate a simple HTML document but found that there was nothing in standard library explicitly for it.  However, C# does have excellent standard library support for XML.</p>
<p>While HTML is not XML, their formats are similar enough that if you know the differences, XML generators can create valid HTML documents easily. For my purposes, installing an external HTML generator would have been an irrelevant dependency on my project, so the built in System.Xml.XmlDocument seemed a better fit.</p>
<p>Below is a code snippet that will create a basic, valid HTML document as a string. I didn&apos;t want to include anything XML specific like namespaces or CDATA tags, so I specifically omitted solutions that required additional configuration to get rid of these. Note that you need to use <code>doc.outerXml</code> to get the string data from the object.  The native <code>doc.toString()</code> will not convert the document to a string.</p>
<pre><code class="language-csharp">using System.Xml;
public string GenerateHtml()
{
	var doc = new XmlDocument();
	var htmlRoot = doc.CreateElement(&quot;html&quot;);
	doc.AppendChild(htmlRoot);
	var body = doc.CreateElement(&quot;body&quot;);
	body.AppendChild(doc.CreateTextNode(&quot;Hello world&quot;));
	htmlRoot.AppendChild(body);
	return doc.OuterXml;
}

</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Use Ghost's API to retrieve posts]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>This blog is running Ghost, a relatively new blogging platform developed with NodeJS. It seemed like a perfect match for this website when I was first starting off because I only needed something lightweight and easy to manage. I also appreciated it being in JavaScript, the main language I use</p>]]></description><link>https://lowrey.me/use-ghosts-api-to-retrieve-posts/</link><guid isPermaLink="false">62784d469397157708f1ca85</guid><dc:creator><![CDATA[Bret Lowrey]]></dc:creator><pubDate>Thu, 14 Dec 2017 21:00:15 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1517273006195-51fa3364bce2?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=d9c2731a9539aabb6098c809fd3810dc" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1517273006195-51fa3364bce2?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=d9c2731a9539aabb6098c809fd3810dc" alt="Use Ghost&apos;s API to retrieve posts"><p>This blog is running Ghost, a relatively new blogging platform developed with NodeJS. It seemed like a perfect match for this website when I was first starting off because I only needed something lightweight and easy to manage. I also appreciated it being in JavaScript, the main language I use to program these days. If I ever needed to hack it, it would certainly be a lot more fun to work with than a lot of the long-in-the-tooth PHP CMSes out there.</p>
<p>When I was looking into gathering some information on my posts, I found Ghost has a ready-to-go JSON REST API readily available.  To use it, you must first enable it in settings:</p>
<p><img src="https://www.ghostforbeginners.com/content/images/2016/10/Labs.jpg" alt="Use Ghost&apos;s API to retrieve posts" loading="lazy"><br>
<img src="https://www.ghostforbeginners.com/content/images/2016/10/Public-API.jpg" alt="Use Ghost&apos;s API to retrieve posts" loading="lazy"></p>
<p>Then it is as simple as requesting this URL <code>https://YOUR_DOMAIN/ghost/api/v0.1/posts/?client_id=ghost-frontend&amp;client_secret=xxxxxxxxxx</code>.  The domain and the client secret will change from installation to installation.  The &apos;client secret&apos; is publically available on all Ghost pages.  You can find it by viewing the source of any Ghost generated page or by looking in the database of your Ghost installation.</p>
<p>Below, I&apos;ve included a quick example using Axios and React using this URL.  I&apos;ve also included the limit option as Ghost by default only returns the first page of results.</p>
<script async src="//jsfiddle.net/lowrey/8pdd6qc0/1/embed/js,result/"></script><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Scraping a book from Kindle (read.amazon.com)]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>After writing my latest post on scraping text from Amazon&apos;s web Kindle reader, I got an idea.  I wondered how hard it would be to scrape an entire book with the method.</p>
<p>Here&apos;s what I came up with:</p>
<pre><code class="language-javascript">function hashString(str){
	let hash = 0;
	for (let</code></pre>]]></description><link>https://lowrey.me/scraping-a-book-from-kindle-read-amazon-com/</link><guid isPermaLink="false">62784d469397157708f1ca84</guid><dc:creator><![CDATA[Bret Lowrey]]></dc:creator><pubDate>Mon, 29 May 2017 15:22:06 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1502126829571-83575bb53030?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=8e8c3b00854dcf8c295ce67d92483a9f" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1502126829571-83575bb53030?ixlib=rb-0.3.5&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;s=8e8c3b00854dcf8c295ce67d92483a9f" alt="Scraping a book from Kindle (read.amazon.com)"><p>After writing my latest post on scraping text from Amazon&apos;s web Kindle reader, I got an idea.  I wondered how hard it would be to scrape an entire book with the method.</p>
<p>Here&apos;s what I came up with:</p>
<pre><code class="language-javascript">function hashString(str){
	let hash = 0;
	for (let i = 0; i &lt; str.length; i++) {
		hash += Math.pow(str.charCodeAt(i) * 31, str.length - i);
		hash = hash &amp; hash; // Convert to 32bit integer
	}
	return hash;
}

var hashes = {};
var content = [];

function addDiv(div){
	let hash  = hashString(div.innerText);
	if (hashes[hash] === undefined) {
		hashes[hash] = true;
		content.push(div.outerHTML);
	}
}

var timeout = null;
function main() {
	var appFrame = document.querySelector(&apos;#KindleReaderIFrame&apos;).contentDocument;
var contentFrames = Array.from(appFrame.querySelectorAll(&apos;iframe&apos;)).map(f =&gt; f.contentDocument);
	Array.from(contentFrames[1].querySelectorAll(&apos;body &gt; div&apos;)).forEach(addDiv);
	timeout = setTimeout(main, 1000);
	appFrame.getElementById(&apos;kindleReader_pageTurnAreaRight&apos;).click()
	console.log(&apos;content&apos;, content.length);
}
main();
</code></pre>
<p>Essentially, this script will turn the page every second and look for any new text in the frame the Kindle app uses to display the book.  The frame is more or less a window of text to buffer.  It doesn&apos;t just contain the text displayed on the screen.  There is no guarantee that going to the next page would add new content to the frame.  However, the app does ensure that only how paragraphs get added or removed and these paragraphs are in order. I reused the string hash code function from an earlier post as an efficient way to see if the string had already been added.</p>
<p>The end result is an array where each element is a paragraph of text.  If you let this run through the entire book, this array will have the entire contents of the book in HTML.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>