<?xml version="1.0" encoding="UTF-8" standalone="no"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" version="2.0">

<channel>
	<title>Stubborn Fanatic - Excerpts Feed</title>
	<atom:link href="https://techblog.viveksanghi.com/feed/" rel="self" type="application/rss+xml"/>
	<link>https://techblog.viveksanghi.com/</link>
	<description>This is an excerpts only feed to Stubborn Fanatic - Vivek Sanghi's tech blog. Please take time to read the articles covering topics like Blogger add-ons/hack code, latest tech news, internet, computers, software and books</description>
	<lastBuildDate>Thu, 01 Oct 2026 08:06:45 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.2</generator>
<site xmlns="com-wordpress:feed-additions:1">5851209</site>	<item>
		<title>The law I cannot read · กฎหมายที่ผมอ่านไม่ออก</title>
		<link>https://techblog.viveksanghi.com/2026/10/thai-law-corpus/</link>
					<comments>https://techblog.viveksanghi.com/2026/10/thai-law-corpus/#respond</comments>
		
		<dc:creator><![CDATA[Vivek Sanghi]]></dc:creator>
		<pubDate>Thu, 01 Oct 2026 08:06:42 +0000</pubDate>
				<category><![CDATA[legal tech]]></category>
		<category><![CDATA[software]]></category>
		<category><![CDATA[ai assisted]]></category>
		<category><![CDATA[data quality]]></category>
		<category><![CDATA[legal research]]></category>
		<category><![CDATA[machine translation]]></category>
		<category><![CDATA[ocr]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[search]]></category>
		<category><![CDATA[stubborn fanatic]]></category>
		<category><![CDATA[thai law]]></category>
		<category><![CDATA[unicode]]></category>
		<guid isPermaLink="false">https://techblog.viveksanghi.com/?p=265</guid>

					<description><![CDATA[<p>I have legal interests in Thailand and cannot read Thai. Building a searchable library of Thai law on my own machine with an AI agent: every English word labelled by how far it can be trusted, PDFs that show one thing and say another, and 27,000 sections machine-translated on a local GPU.</p>
<p>The post <a href="https://techblog.viveksanghi.com/2026/10/thai-law-corpus/">The law I cannot read · กฎหมายที่ผมอ่านไม่ออก</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<!--
  The law I cannot read / กฎหมายที่ผมอ่านไม่ออก  (techblog.viveksanghi.com)
  Paste this whole block into a WordPress "Custom HTML" block (or the Classic
  editor's Text/HTML mode). Both languages sit in the page's HTML at all times,
  so crawlers index both; the tabs only change what is shown. The switch is
  CSS-only (no JavaScript), same pattern as the Pushp Ki Abhilasha post, with
  its own "tlc-" prefix so the two posts' styles never collide.
  Default open tab: English (the "checked" attribute on #tlc-en). Move
  "checked" to #tlc-th to open in Thai instead.
-->
<style>
  @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Thai:wght@400;600&family=Sarabun:wght@400;600&display=swap');
  .tlc-wrap { max-width: 700px; margin: 2em auto; line-height: 1.75; }
  .tlc-wrap p { margin: 0 0 1.1em; }
  .tlc-wrap h3 { margin: 1.8em 0 .6em; font-size: 1.15em; font-weight: 600; }
  .tlc-wrap ul, .tlc-wrap ol { margin: 0 0 1.1em 1.2em; padding: 0; }
  .tlc-wrap li { margin: 0 0 .4em; }
  .tlc-wrap [lang="th"] { font-family: "Noto Sans Thai", "Sarabun", sans-serif; }
  .tlc-wrap pre {
    margin: 0 0 1.4em; padding: .9em 1.1em; overflow-x: auto;
    border-left: 3px solid currentColor; background: rgba(127,127,127,.12);
    font-size: .85em; line-height: 1.5; white-space: pre;
  }
  .tlc-wrap a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
  .tlc-wrap code { font-family: Consolas, "SFMono-Regular", Menlo, "DejaVu Sans Mono", monospace; font-size: .92em; }
  .tlc-wrap pre code { font-size: 1em; }

  .tlc-visually-hidden {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
  }

  .tlc-tabs { display: flex; gap: 1.5em; margin: 0 0 1.4em; border-bottom: 1px solid currentColor; opacity: .9; }
  .tlc-tabs label {
    cursor: pointer; padding: .4em 0; font-weight: 600;
    border-bottom: 2px solid transparent; margin-bottom: -1px;
  }
  input[name="tlc-lang"] { position: absolute; opacity: 0; pointer-events: none; }
  .tlc-panel { display: none; }

  #tlc-en:checked ~ .tlc-tabs label[for="tlc-en"],
  #tlc-th:checked ~ .tlc-tabs label[for="tlc-th"] {
    border-bottom-color: currentColor;
  }
  #tlc-en:checked ~ .tlc-content #tlc-panel-en,
  #tlc-th:checked ~ .tlc-content #tlc-panel-th {
    display: block;
  }
</style>

<div class="tlc-wrap">
  <input type="radio" name="tlc-lang" id="tlc-en" checked>
  <input type="radio" name="tlc-lang" id="tlc-th">

  <div class="tlc-tabs">
    <label for="tlc-en">English</label>
    <label for="tlc-th" lang="th">ไทย</label>
  </div>

  <div class="tlc-content">

    <div id="tlc-panel-en" class="tlc-panel" lang="en">
      <h2 class="tlc-visually-hidden">The law I cannot read</h2>
      <p>I have legal interests in Thailand, and I cannot read Thai.</p>
      <p>That is an awkward position for a lawyer. Thailand&#8217;s statutes are published in Thai, and the Thai text is the law. Anything in English is somebody&#8217;s reading of it. For the Codes that govern property, family and succession, the Thai government&#8217;s own legal drafting office holds no English text at all. I checked, and how I checked is part of this post.</p>
      <p>So I built a second law library on my own computer. The first one, of Indian statutes and judgments, is <a href="https://techblog.viveksanghi.com/2026/09/building-a-local-indian-law-corpus/">a separate project</a>. This one holds Thai law in Thai, with every line of English labelled by how far it can be trusted, and it answers a search in a fraction of a second.</p>
      <p>I built it with Claude, Anthropic&#8217;s AI model, working as an agent: it could run programs on my computer and use my Chrome browser, under rules I set. I made the decisions. The scripts and most of the investigation came from the model, and so did a good number of the mistakes. The work started on 4 September 2026; most of it ran in the last week of September.</p>
      <p>One thing to say before anything else: I am an Indian advocate, not a Thai lawyer. This is a post about building a research tool, not advice on Thai law. There is a fuller note at the end.</p>
      <h3>Every English word gets a label</h3>
      <p>The first decision was a rule about English.</p>
      <p>Thailand is a civil law country. Thai courts are not formally bound by earlier decisions, the Supreme Court&#8217;s included, though those decisions carry weight. The statute book matters more than case law, and the statute book is Thai. If I am going to rely on any of this, every English sentence has to say where it came from.</p>
      <p>So there are four tiers:</p>
      <pre><code>T1  Thai official text, or English the agency published as part of the instrument
T2  English published by a Thai agency, marked unofficial
T3  third-party English: commercial translations, law firm sites
T4  machine output, ours: for finding and reading only, never quoted, never cited</code></pre>
      <p>Every text file carries a <code>tier:</code> line in its header, and the search tool prints the tier beside every result. Machine English comes out as <code>T4 machine translation - NEVER quote or cite</code>, in capitals, every time. That label is what stops a fluent sentence from turning into a citation.</p>
      <h3>Finding the statute book</h3>
      <p>The Office of the Council of State publishes the official consolidated text of Thai laws. Its old address, krisdika.go.th, no longer served it; the office had moved to ocs.go.th.</p>
      <p>The new site&#8217;s search page is backed by a form-encoded endpoint, <code>POST /searchlaw/indexs/list_table_search</code>. Two things stood between it and a clean list.</p>
      <p>The first was TLS. The server does not send its intermediate certificate. A browser fetches the missing certificate itself; Python&#8217;s <code>requests</code> does not, and fails. Setting <code>verify=False</code> would have silenced the error by switching the check off. The fix used instead was <code>truststore</code>, which hands verification to Windows&#8217; own certificate chain building, and Windows can find the missing intermediate. The check still happens.</p>
      <p>The second was a filter. The site&#8217;s own search page sends the parameter <code>query[lawCategoryName]</code> with the value <code>1B,1C</code>. Sent that way, the index hid 7 of 147 records. Sent empty, all 147 came back. I would not have known without counting both ways.</p>
      <p>That gave titles and PDFs, not text. The text sits behind a viewer, an Angular single-page app, and five guessed endpoints returned 404. What worked was opening the viewer in a browser and reading how the page itself asks the server for a law, from its network traffic and its JavaScript bundle. The viewer uses a public JSON endpoint:</p>
      <pre><code>POST https://searchlaw.ocs.go.th/ocs-api/public/doc/getLawDoc
Content-Type: application/json

{ "reqHeader": { ...request id, channel, timestamp, service name... },
  "reqBody":   { "timelineId": "&lt;id from the index&gt;", "isTransEng": false,
                 "sectionIds": [], "sectionAndExplains": [] } }</code></pre>
      <p>What comes back is structured: each section with its number, label and HTML content, plus every version of that law. For the Civil and Commercial Code that was 2,254 entries and 605,706 characters, in force from 25 March 2025. File downloads on the same site go through a route that needs a token the viewer holds. The token was never read or reused: the English PDFs mentioned below were saved through the viewer&#8217;s own download button, as any reader would save them.</p>
      <h3>The English that was not there</h3>
      <p>The same request has a flag, <code>isTransEng</code>. Set to true, it asks for English.</p>
      <p>For all eight Thai Codes, the answer was <code>SUCCESS</code> with zero sections. The system knows the English names of the Codes. It holds none of their English text.</p>
      <p>There was one exception. The Civil and Commercial Code&#8217;s timeline has 73 versions, and one of them, the original 1925 text, is marked as having a translation: a 242 KB file. For a week the working assumption was that this was an English translation of the 1925 Code. When the file came down and was read, its title page said otherwise. It is the Act Amending the Civil and Commercial Code (No. 20), B.E. 2557 (2014): one amending Act, filed against the original version.</p>
      <p>The note was corrected with the date of the correction, and the earlier reading left on record. A timeline entry tells you a file exists. Only the file tells you what it is.</p>
      <p>The Council of State does hold English for other laws, and 70 such PDFs, covering 57 laws, are now in the library. They are T2, and they say on their face that the Thai is the sole authority. Many turned out to be amending Acts rather than the main Act, which is one more reason the library reads the Thai consolidation first.</p>
      <h3>A section 2 that appeared 36 times</h3>
      <p>With the endpoint working, the eight Codes came down as structured text: 5,526 entries, of which 4,453 are numbered provisions. Then the counting started, and the counting is where the bugs were.</p>
      <p>Section 2 appeared 36 times in the Civil and Commercial Code file. Each Code&#8217;s file carries the Acts that amended it, appended at the end, and each of those Acts numbers its own sections from 1. Left as it was, a search for section 2 would find 36 candidates and one of them would be right. Every entry now carries a flag saying whether it belongs to the Code itself. That is how the 2,254 entries in the Civil and Commercial Code file come down to its 1,850 provisions; the rest are headings and the appended Acts.</p>
      <p>Some of the others:</p>
      <ul>
        <li>The Land Code has eight entries in a row all labelled section 105. They are 105, 105 bis, ter and on to octies. The label field cuts the number short, so the real reference is parsed from the text.</li>
        <li>The same ordinal is spelled two ways: <code>อัฎฐ</code> with ฎ in the Civil Procedure Code, <code>อัฏฐ</code> with ฏ in the Land Code.</li>
        <li>The site served escaped HTML comments, <code>&amp;lt;!--[endif]--&amp;gt;</code>, which only became comments after unescaping, so the comment-stripper never saw them. The selftest had passed because its fixture used a real comment: it was written from the fix, not from the data.</li>
      </ul>
      <p>Once all of these were fixed, no reference in any of the eight Codes appeared twice.</p>
      <h3>When a repealed law is still current</h3>
      <p>Each version in a law&#8217;s timeline carries a <code>stateId</code>. <code>01</code> looked like &#8220;in force&#8221; and <code>00</code> like &#8220;superseded&#8221;.</p>
      <p>Of the 652 index rows whose titles carry the repeal marker <code>(ยกเลิก)</code>, 638 sit at <code>01</code>. The last version of a repealed law is still that law&#8217;s current version. So repeal is read from the title marker, never from the state.</p>
      <p>A probe on 24 September found a third state, <code>02</code>: published but not yet in force, with a fifth version type, a future consolidation showing the law as it will read once a pending amendment starts. On 30 September that turned up a trap. A value-added tax decree showed a future consolidation in force from 1 October 2026, and it left out a separate amending decree of the same date that keeps the reduced rate for another year. Read alone, the future text would have given the wrong rate. The library now holds the amending decree separately and flags any future version that matches the current one.</p>
      <p>That same day the whole library was checked against the Council of State: 837 timeline entries under the parent laws held, and all 773 laws unchanged since they were fetched. The consolidations themselves can trail behind amendments. The Revenue Code&#8217;s latest consolidation on the site is dated 9 November 2021. The site cannot show an amendment it has not yet worked in, so the issuing department is the cross-check.</p>
      <h3>Working with an agent that can be wrong</h3>
      <p>An agent that can run programs on my computer and drive my browser can do a great deal in an afternoon. It can also be wrong about a file it has not looked at, and it writes fluent explanations of things that did not happen. The rules I work under came out of incidents.</p>
      <p>The separation rule came first. The Thai library sits beside the Indian one on the same disk, and early on a Thai backup was written into the Indian backup folder. Nothing was lost, but since then a guard refuses any path under the Indian backups, and a checker audits every script for where it can reach. Building that checker produced two of the stranger moments of the month:</p>
      <ul>
        <li><code>io.open(path, "w", newline=...)</code> empties the file before it raises on a bad <code>newline</code> argument. The checker truncated itself to zero bytes that way. Everything now writes to a temporary file and <code>os.replace</code>s it.</li>
        <li>A Windows path such as <code>D:\...\Thai_Backup</code> is, to a Linux program, an ordinary file name. Run from the agent&#8217;s side, a backup created a folder whose name was the whole Windows path, inside the library. It looked like a successful backup.</li>
      </ul>
      <p>The second rule is that every claim has to be shown. If the agent says a backup exists, it shows the listing. If it asks me to run a file, it first opens that file and quotes the line that does the work. That rule came after a session with five errors of this kind, one of which was a recommendation to run a batch file that would have thrown away three days of work.</p>
      <p>The third is a backup before anything that rewrites a file. Once, a batch file suggested in passing rewrote a file another script had written its OCR results into, and hours of machine time went with it. Now a copy comes first, and both checksums are shown. On 1 October that rule ran on something small: one translation cache file carried 1,443 NUL bytes left over from an interrupted run on 29 September. Nothing was lost, since the work had been redone, but the copy was taken, the bad line removed and all 776 cache files scanned before the index was rebuilt.</p>
      <p>The agent works within limits I set. It uses the sites&#8217; public pages and endpoints only, respects robots.txt, does not solve CAPTCHAs, enter passwords or handle login tokens, and anything I have to do myself comes as numbered steps from the first click. When it saves a file from a page, the page builds the file and a click on a link saves it to my Downloads folder.</p>
      <h3>PDFs that show one thing and say another</h3>
      <p>Not everything comes through the endpoint. Ministerial regulations, schedules and forms come as PDFs, and a PDF can look perfect on screen while its text layer says something else. The page is drawn from the glyph outlines in the embedded font; text extraction goes through a separate mapping from character codes to Unicode (the ToUnicode map, or the font&#8217;s encoding and glyph names when there is no map). If that mapping is wrong, the screen is right and the text is junk.</p>
      <p>Three faults turned up.</p>
      <p>Broken glyph maps. The outlines were intact; only the map was broken. So every glyph outline was hashed, hash-to-character was learned from pages whose maps are sound, and the junk glyphs were looked up by shape. On the first 29 junk pages that gave 27,277 glyphs and 88 distinct shapes, all decoded. One bug worth remembering: the font cache was keyed by PDF object id, and object ids repeat across files.</p>
      <p>Mac Thai. Seven files decoded at 0% through the glyph table. All used one font pair, and the glyph names inside were Mac Roman names standing at the positions of Apple&#8217;s old Mac OS Thai encoding. The text was Thai written in Mac Thai and read back as Mac Roman. The repair is a table:</p>
      <pre><code># simplified; Python has no mac_thai codec
MAC_EXTRA = {0x83: "\u0E48", 0x88: "\u0E48", 0x89: "\u0E49", 0x8C: "\u0E4C",
             0x92: "\u0E31", 0x93: "\u0E47", 0x94: "\u0E34", 0x95: "\u0E35",
             0x97: "\u0E37", 0x8D: "\u201C", 0x8E: "\u201D"}

def macthai_decode(s):
    out = []
    for ch in s:
        try:
            b = ch.encode("mac_roman")[0]
        except UnicodeEncodeError:
            out.append("\uFFFD"); continue
        if b &lt; 0x80:
            out.append(ch)
        elif 0xA1 &lt;= b &lt;= 0xFB:            # Thai letters as in TIS-620; Apple's dashes and
                                           # symbols in this range differ and come out unknown
            out.append(bytes([b]).decode("cp874", errors="replace"))
        elif b in MAC_EXTRA:               # Apple's extra bytes, read from the pages
            out.append(MAC_EXTRA[b])
        else:
            out.append("\uFFFD")           # counted as unknown, never guessed
    return "".join(out)</code></pre>
      <p>The first trial dropped every ส. Mac Roman 0xCA is a no-break space; in Mac Thai, 0xCA is ส, and the first trial read it as a space. A wrong decode can also come out looking like Thai, so a decoded page is accepted only if Thai spelling holds: a vowel or tone mark must follow a consonant, the leading vowels must precede one, and the violation rate must be under 1%.</p>
      <p>Sara aa written as sara am. Ten PDFs, 30 pages, wrote every า as ำ, and every real ำ with a space in front of it: <code>กำร</code> for <code>การ</code>, <code>ก ำหนด</code> for <code>กำหนด</code>. The page test called them Thai, because they are Thai letters. The repair is exact:</p>
      <pre><code># simplified
import re
MARK = "\uE000"
def fix_sara_aa(t):
    t = re.sub(r" ([\u0E48-\u0E4B]?)\u0E33", lambda m: m.group(1) + MARK, t)  # " ำ" is the real ำ
    return t.replace("\u0E33", "\u0E32").replace(MARK, "\u0E33")             # every other ำ is า</code></pre>
      <p>Detection is by impossible spellings, <code>กำร</code>, <code>ตำม</code>, <code>จำก</code> and a few more, appearing at least twice with none of the correct forms on the page. On the PDF folder it selected exactly those 30 pages. One of the ten files was the Land Code regulation on registration fees for transfers and mortgages.</p>
      <p>Then a check against the official source became possible. Seventy-three regulations under the main Acts came back as official text through the endpoint, with the same identifiers as PDFs already read. The measure is the share of each PDF page&#8217;s Thai letters found, in order, in the official text:</p>
      <pre><code>method     pages   median
macthai      24    0.984
glyph        31    0.978
pua          38    0.978
layer       108    0.942
ocr         160    0.717</code></pre>
      <p><code>layer</code> is a text layer taken as it is, <code>pua</code> a layer using the Unicode private-use area. Every page below 0.8 that was not one of the sara aa pages was a form or an annex: the official text holds the operative regulation only, and the PDF is the only source for forms and schedules. The 0.717 median for Tesseract on scanned Thai is why OCR text sits at T4. Thai numerals are the worst of it.</p>
      <h3>Machine English I will not quote</h3>
      <p>Accurate Thai I cannot read is of limited use to me. So every section has a machine English rendering, made locally by Google&#8217;s open Gemma model (<code>gemma3:27b</code>) through Ollama on an RTX 5090. The 26,978 sections were split into 27,198 pieces for translation, and the run log records about 36 hours of model time between the evening of 28 September and the afternoon of 30 September.</p>
      <p>All of it is T4. The checks were there to make it good enough to search with and to read beside the Thai.</p>
      <ol>
        <li>Refusal at translation time: an answer is refused if it is empty, mostly Thai, far too short or far too long for the Thai it came from.</li>
        <li>Mechanical checks on every piece after every run: numbers in the Thai missing from the English; a section number that differs; a suffix section (<code>มาตรา ๓ ตรี</code>) not rendered as &#8220;3 ter&#8221;; fewer numbered items than the Thai; runaway repetition; chatter such as &#8220;Here is the translation&#8221;. A failure goes back with the reason written into the retry prompt, at most twice; after that it is listed for reading.</li>
        <li>A sample sheet of 30 random pieces, Thai beside English, at the end of every report.</li>
        <li>A termbase adherence table: for each fixed legal term, how often the English used the agreed rendering, and the Thai contexts the term turned up in.</li>
      </ol>
      <p>The fourth caught the best mistake of the month. The termbase said <code>อาศัย</code> means &#8220;habitation&#8221;, as in a right of habitation. Only 24% of pieces followed it, and the commonest context was <code>อาศัยอำนาจ</code>, &#8220;by virtue of the power&#8221;. The word inside other words was being translated as habitation. Land Code section 96 bis came out as aliens obtaining land &#8220;through habitation based on a treaty&#8221;; the Thai says by virtue of a treaty. The entry was narrowed to <code>สิทธิอาศัย</code>, right of habitation, and the bad renderings fell from 202 to 6, each of the 6 a proper use.</p>
      <p>Flagged pieces fell from 83 on the first pass to 7 on the last. Claude read those 7 against the Thai: six are real omissions of a number or a cross-reference and are listed as known defects, and one is a typo in the source. That is a machine checking a machine, which is the reason none of it is ever quoted.</p>
      <h3>One file, a fifth of a second</h3>
      <p>The Indian library runs on parquet files and DuckDB, because a million judgments need it. This one is a single SQLite file with two FTS5 indexes.</p>
      <ul>
        <li>Thai uses the <code>trigram</code> tokenizer (SQLite 3.34 or later). Thai is written without spaces between words, and trigram matching needs no word-splitting. A query term shorter than three code points cannot use the trigram index, so it falls back to <code>LIKE</code>. Thai vowels and tone marks are separate code points, so ที่ is three of them.</li>
        <li>English uses Porter stemming with prefix matching, a small synonym list (sale/sell, land/immovable, wife/spouse, foreigner/alien), and the termbase: an English phrase that matches a termbase rendering also searches the Thai term, at half weight.</li>
      </ul>
      <p>SQLite could not write its database file on the folder as the agent&#8217;s environment mounts it; the first attempt failed with <code>disk I/O error</code>. So the index is built in memory and written out in one go:</p>
      <pre><code># simplified; serialize() needs Python 3.11+
import os, sqlite3
mem = sqlite3.connect(":memory:")
mem.execute("CREATE VIRTUAL TABLE fts_th USING fts5(text_th, tokenize='trigram')")
# ... load sections, agency texts, notes ...
with open(tmp_path, "wb") as f:
    f.write(mem.serialize())
os.replace(tmp_path, index_path)   # the old index survives until the new one is complete</code></pre>
      <p>A search looks like this. The square brackets mark the words that matched:</p>
      <pre><code>python3 th_ask.py "matrimonial property"

Civil and Commercial Code, section 1474
  TH  [T1 Thai, Council of State consolidation, in force from 2025-03-25]
      มาตรา ๑๔๗๔ สินสมรสได้แก่ทรัพย์สิน (๑) ที่คู่สมรสได้มาระหว่างสมรส ...
  EN  [T4 machine translation - NEVER quote or cite]
      Section 1474 [Matrimonial] [property] consists of [property] (1) which
      the spouses obtained during marriage. ...</code></pre>
      <p>As of 1 October 2026: 26,978 sections, all with machine English; 1,218 third-party English pages; about 8,400 chunks of agency, tax, OCR, treaty and note text. The index is 146 MB, a full rebuild takes about 11 seconds, and a search returns in 0.1 to 0.2 seconds. I looked at moving it to the parquet-and-DuckDB layout of the Indian library and left it as it is. That layout exists for a 5.8 GB judgment index; an 11-second rebuild gains nothing from it.</p>
      <h3>Case law, later</h3>
      <p>Thai Supreme Court decisions are not in the library yet. That is a choice of order. In a civil law system a Supreme Court decision is not a precedent that binds; it carries persuasive weight and helps in reading the Code. The statute book had to come first. Case law is on the list for later, as a targeted set of decisions on property, family and succession rather than a bulk download.</p>
      <h3>What I check before relying on it</h3>
      <ul>
        <li>The machine English. It is for finding and reading. Anything to be acted on needs an official English text where one exists, or a reading of the Thai by someone who reads it.</li>
        <li>The consolidations. They trail behind amendments; the issuing department is the cross-check.</li>
        <li>OCR text. It finds the page; then the page is read.</li>
        <li>Forms and schedules. They exist only in the PDFs, and some of those PDFs are still unread.</li>
        <li>The Bank of Thailand&#8217;s PDFs. 1,821 pages with junk text layers are measured and not solved. An improved glyph method can hash 98% of their glyphs, but more than half of those glyphs never appear on a clean page to learn from.</li>
      </ul>
      <h3>What this is for</h3>
      <p>I wanted to start any question of Thai law that touches my own affairs from the Thai text, with an honest label on every English word I lean on. The library does that now. A search gives me a Thai section, a T4 rendering to tell me what it says, and an official source to confirm it in.</p>
      <h3>A note on what this is not</h3>
      <p>I am an advocate enrolled in India. I am not a Thai lawyer, I do not practise Thai law, and as a foreign national I could not. Thai law allows only Thai nationals to register as lawyers (Lawyers Act B.E. 2528, section 35), and legal and litigation services are among the work Thai law reserves to Thai nationals, with narrow exceptions for arbitration. This library is a personal research tool for my own affairs. Nothing in this post is advice on Thai law. For any Thai-law matter, consult a lawyer licensed in Thailand.</p>
      <p>The texts in the library are public: Thai legislation, regulations, official notices and judgments, and translations of them made by Thai state agencies, are outside copyright under the Thai Copyright Act (section 7). Third-party English is held for private research only and is not reproduced here.</p>
      <h3>Glossary</h3>
      <p><strong>B.E.</strong>: Buddhist Era. Thai dates count from it; B.E. = C.E. + 543, so B.E. 2569 is 2026.</p>
      <p><strong>Council of State (ocs.go.th)</strong>: the Thai government&#8217;s legal drafting office, which publishes consolidated texts of Thai laws.</p>
      <p><strong>Consolidation</strong>: the text of a law with its amendments worked in. A convenience; the law itself is what was published in the Royal Gazette.</p>
      <p><strong>Royal Gazette</strong>: the official gazette in which Thai laws are published and from which they take effect.</p>
      <p><strong>มาตรา</strong>: section. <strong>bis, ter, quater</strong>: inserted sections numbered after an existing one.</p>
      <p><strong>Termbase</strong>: a fixed list of Thai legal terms and their agreed English renderings, used to keep the machine translation consistent.</p>
      <p><strong>FTS5</strong>: SQLite&#8217;s full-text search extension. <strong>Trigram tokenizer</strong>: indexes every run of three characters, so text without word boundaries can be searched without splitting it into words.</p>
      <p><strong>ToUnicode map</strong>: the table inside a PDF that tells software which Unicode character each character code stands for. When it is wrong, a page looks right and extracts as nonsense.</p>
      <p><strong>Mac Thai</strong>: Apple&#8217;s old 8-bit encoding for Thai, close to TIS-620 for the letters, with extra bytes for tone marks and vowels.</p>
      <p><strong>OCR</strong>: optical character recognition. Tesseract is the open-source OCR engine used here.</p>
    </div>

    <div id="tlc-panel-th" class="tlc-panel" lang="th">
      <h2 class="tlc-visually-hidden">กฎหมายที่ผมอ่านไม่ออก</h2>
      <p>ผมมีผลประโยชน์ทางกฎหมายอยู่ในประเทศไทย แต่ผมอ่านภาษาไทยไม่ออก</p>
      <p>สำหรับคนเป็นนักกฎหมาย นี่เป็นสถานการณ์ที่ลำบากใจ กฎหมายไทยประกาศใช้เป็นภาษาไทย และตัวบทภาษาไทยคือตัวกฎหมาย ฉบับภาษาอังกฤษใด ๆ เป็นเพียงการอ่านของใครคนหนึ่ง สำหรับประมวลกฎหมายที่ว่าด้วยทรัพย์สิน ครอบครัว และมรดก หน่วยงานยกร่างกฎหมายของรัฐบาลไทยเองไม่มีตัวบทภาษาอังกฤษเลย ผมตรวจสอบแล้ว และวิธีที่ผมตรวจสอบก็เป็นส่วนหนึ่งของบทความนี้</p>
      <p>ผมจึงสร้างห้องสมุดกฎหมายแห่งที่สองขึ้นในคอมพิวเตอร์ของตัวเอง แห่งแรกเป็นตัวบทและคำพิพากษาของอินเดีย ซึ่งเป็น<a href="https://techblog.viveksanghi.com/2026/09/building-a-local-indian-law-corpus/">อีกโครงการหนึ่ง</a> แห่งนี้เก็บกฎหมายไทยเป็นภาษาไทย ภาษาอังกฤษทุกบรรทัดมีป้ายบอกว่าเชื่อถือได้แค่ไหน และค้นหาได้ในเสี้ยววินาที</p>
      <p>ผมสร้างมันร่วมกับ Claude โมเดล AI ของ Anthropic ซึ่งทำงานในฐานะเอเจนต์ คือสั่งรันโปรแกรมบนคอมพิวเตอร์ของผมและใช้เบราว์เซอร์ Chrome ของผมได้ ภายใต้กฎที่ผมกำหนด การตัดสินใจเป็นของผม สคริปต์และการสืบค้นส่วนใหญ่มาจากโมเดล และความผิดพลาดจำนวนไม่น้อยก็เช่นกัน งานเริ่มเมื่อวันที่ 4 กันยายน 2026 และส่วนใหญ่ทำในสัปดาห์สุดท้ายของเดือนกันยายน</p>
      <p>ขอกล่าวไว้ก่อนเรื่องอื่น ผมเป็นทนายความอินเดีย ไม่ใช่ทนายความไทย บทความนี้เล่าเรื่องการสร้างเครื่องมือค้นคว้า ไม่ใช่คำแนะนำเกี่ยวกับกฎหมายไทย รายละเอียดอยู่ในหมายเหตุท้ายบทความ</p>
      <h3>ภาษาอังกฤษทุกคำต้องมีป้าย</h3>
      <p>การตัดสินใจแรกคือกฎว่าด้วยภาษาอังกฤษ</p>
      <p>ประเทศไทยใช้ระบบซีวิลลอว์ ศาลไทยไม่ผูกพันตามคำพิพากษาก่อนหน้าอย่างเป็นทางการ รวมถึงคำพิพากษาศาลฎีกา แม้คำพิพากษาเหล่านั้นจะมีน้ำหนักก็ตาม ตัวบทกฎหมายจึงสำคัญกว่าคำพิพากษา และตัวบทเป็นภาษาไทย ถ้าผมจะพึ่งพาสิ่งเหล่านี้ ประโยคภาษาอังกฤษทุกประโยคต้องบอกได้ว่ามาจากไหน</p>
      <p>จึงแบ่งเป็นสี่ระดับ:</p>
      <pre><code>T1  ตัวบทภาษาไทยที่เป็นทางการ หรือภาษาอังกฤษที่หน่วยงานเผยแพร่เป็นส่วนหนึ่งของตัวกฎหมาย
T2  ภาษาอังกฤษที่หน่วยงานรัฐของไทยเผยแพร่ โดยระบุว่าไม่เป็นทางการ
T3  ภาษาอังกฤษจากบุคคลภายนอก เช่น คำแปลเชิงพาณิชย์ เว็บไซต์สำนักงานกฎหมาย
T4  ผลงานแปลด้วยเครื่องของเราเอง ใช้เพื่อค้นหาและอ่านเท่านั้น ห้ามยกอ้าง ห้ามอ้างอิง</code></pre>
      <p>ไฟล์ข้อความทุกไฟล์มีบรรทัด <code>tier:</code> ในส่วนหัว และเครื่องมือค้นหาพิมพ์ระดับไว้ข้างผลลัพธ์ทุกรายการ ภาษาอังกฤษที่แปลด้วยเครื่องจะขึ้นป้าย <code>T4 machine translation - NEVER quote or cite</code> เป็นตัวพิมพ์ใหญ่ทุกครั้ง ป้ายนี้คือสิ่งที่กันไม่ให้ประโยคที่อ่านลื่นไหลกลายเป็นข้อความที่ถูกนำไปอ้างอิง</p>
      <h3>ตามหาตัวบท</h3>
      <p>สำนักงานคณะกรรมการกฤษฎีกาเผยแพร่ตัวบทกฎหมายไทยฉบับปรับปรุงอย่างเป็นทางการ ที่อยู่เดิม krisdika.go.th ไม่ได้ให้บริการแล้ว หน่วยงานย้ายไปที่ ocs.go.th</p>
      <p>หน้าค้นหาของเว็บไซต์ใหม่ทำงานบน endpoint แบบ form-encoded คือ <code>POST /searchlaw/indexs/list_table_search</code> มีสองเรื่องที่ขวางอยู่ก่อนจะได้รายการที่ครบถ้วน</p>
      <p>เรื่องแรกคือ TLS เซิร์ฟเวอร์ไม่ส่งใบรับรองระดับกลาง (intermediate certificate) มาด้วย เบราว์เซอร์ไปดึงใบรับรองที่ขาดมาเองได้ แต่ไลบรารี <code>requests</code> ของ Python ทำไม่ได้และล้มเหลว การตั้ง <code>verify=False</code> จะทำให้ข้อผิดพลาดหายไปด้วยการปิดการตรวจสอบ วิธีที่ใช้แทนคือ <code>truststore</code> ซึ่งส่งงานตรวจสอบไปให้ระบบสร้างสายการรับรองของ Windows เอง และ Windows หาใบรับรองระดับกลางที่ขาดได้ การตรวจสอบจึงยังคงเกิดขึ้น</p>
      <p>เรื่องที่สองคือตัวกรอง หน้าค้นหาของเว็บไซต์ส่งพารามิเตอร์ <code>query[lawCategoryName]</code> ด้วยค่า <code>1B,1C</code> เมื่อส่งแบบนั้น ดัชนีซ่อนไป 7 รายการจาก 147 รายการ เมื่อส่งเป็นค่าว่าง ได้ครบทั้ง 147 รายการ ถ้าไม่นับทั้งสองแบบ ผมคงไม่รู้</p>
      <p>ผลที่ได้คือชื่อกฎหมายและไฟล์ PDF แต่ยังไม่ใช่ตัวบท ตัวบทอยู่หลังหน้าแสดงผลซึ่งเป็นแอป Angular แบบหน้าเดียว และ endpoint ห้าแห่งที่ลองเดาได้ผล 404 ทั้งหมด สิ่งที่ได้ผลคือเปิดหน้าแสดงผลในเบราว์เซอร์ แล้วอ่านดูว่าหน้าเว็บขอตัวบทจากเซิร์ฟเวอร์อย่างไร จากการรับส่งข้อมูลบนเครือข่ายและจาก JavaScript bundle ของมัน หน้าแสดงผลใช้ endpoint JSON สาธารณะ:</p>
      <pre><code>POST https://searchlaw.ocs.go.th/ocs-api/public/doc/getLawDoc
Content-Type: application/json

{ "reqHeader": { ...request id, channel, timestamp, service name... },
  "reqBody":   { "timelineId": "&lt;id from the index&gt;", "isTransEng": false,
                 "sectionIds": [], "sectionAndExplains": [] } }</code></pre>
      <p>คำตอบที่ได้เป็นข้อมูลมีโครงสร้าง แต่ละมาตรามีเลขมาตรา ป้าย และเนื้อหา HTML พร้อมทุกฉบับของกฎหมายนั้น สำหรับประมวลกฎหมายแพ่งและพาณิชย์ ได้ 2,254 รายการ 605,706 ตัวอักษร ใช้บังคับตั้งแต่วันที่ 25 มีนาคม 2025 การดาวน์โหลดไฟล์บนเว็บไซต์เดียวกันใช้อีกเส้นทางหนึ่งที่ต้องมีโทเคนซึ่งหน้าแสดงผลถืออยู่ โทเคนนั้นไม่เคยถูกอ่านหรือนำไปใช้ซ้ำ ไฟล์ PDF ภาษาอังกฤษที่กล่าวถึงด้านล่างบันทึกผ่านปุ่มดาวน์โหลดของหน้าแสดงผลเอง เหมือนผู้อ่านทั่วไปบันทึกไฟล์</p>
      <h3>ภาษาอังกฤษที่ไม่มีอยู่จริง</h3>
      <p>คำขอเดียวกันนี้มีแฟล็ก <code>isTransEng</code> เมื่อตั้งเป็น true จะเป็นการขอภาษาอังกฤษ</p>
      <p>สำหรับประมวลกฎหมายไทยทั้งแปดฉบับ คำตอบคือ <code>SUCCESS</code> แต่มีศูนย์มาตรา ระบบรู้จักชื่อภาษาอังกฤษของประมวลกฎหมายเหล่านี้ แต่ไม่มีตัวบทภาษาอังกฤษเลย</p>
      <p>มีข้อยกเว้นอยู่หนึ่งกรณี ไทม์ไลน์ของประมวลกฎหมายแพ่งและพาณิชย์มี 73 ฉบับ และหนึ่งในนั้น คือตัวบทดั้งเดิมปี 1925 มีเครื่องหมายว่ามีคำแปล เป็นไฟล์ขนาด 242 KB ตลอดหนึ่งสัปดาห์ สมมติฐานในการทำงานคือไฟล์นี้เป็นคำแปลภาษาอังกฤษของประมวลฯ ฉบับปี 1925 เมื่อดาวน์โหลดไฟล์มาอ่าน หน้าปกบอกไว้อีกอย่าง มันคือพระราชบัญญัติแก้ไขเพิ่มเติมประมวลกฎหมายแพ่งและพาณิชย์ (ฉบับที่ 20) พ.ศ. 2557 เป็นพระราชบัญญัติแก้ไขเพิ่มเติมเพียงฉบับเดียว ที่ถูกจัดเก็บไว้กับฉบับดั้งเดิม</p>
      <p>บันทึกจึงได้รับการแก้ไข พร้อมวันที่แก้ และการอ่านเดิมยังคงถูกเก็บไว้ รายการในไทม์ไลน์บอกได้เพียงว่ามีไฟล์อยู่ ตัวไฟล์เองเท่านั้นที่บอกว่ามันคืออะไร</p>
      <p>สำนักงานคณะกรรมการกฤษฎีกามีภาษาอังกฤษสำหรับกฎหมายฉบับอื่น และตอนนี้ PDF เหล่านั้น 70 ไฟล์ ครอบคลุมกฎหมาย 57 ฉบับ อยู่ในห้องสมุดแล้ว จัดอยู่ในระดับ T2 และระบุไว้ในตัวเองว่าภาษาไทยเป็นฉบับที่มีผลบังคับแต่ผู้เดียว หลายไฟล์กลายเป็นพระราชบัญญัติแก้ไขเพิ่มเติม ไม่ใช่ตัวพระราชบัญญัติหลัก ซึ่งเป็นอีกเหตุผลหนึ่งที่ห้องสมุดนี้อ่านตัวบทฉบับปรับปรุงภาษาไทยก่อนเสมอ</p>
      <h3>มาตรา 2 ที่ปรากฏ 36 ครั้ง</h3>
      <p>เมื่อ endpoint ใช้งานได้ ประมวลกฎหมายทั้งแปดฉบับก็ถูกดึงลงมาเป็นข้อความมีโครงสร้าง ได้ 5,526 รายการ ในจำนวนนี้เป็นบทบัญญัติที่มีเลขกำกับ 4,453 รายการ จากนั้นการนับก็เริ่มขึ้น และบั๊กอยู่ในการนับนั่นเอง</p>
      <p>มาตรา 2 ปรากฏ 36 ครั้งในไฟล์ประมวลกฎหมายแพ่งและพาณิชย์ ไฟล์ของประมวลกฎหมายแต่ละฉบับมีพระราชบัญญัติที่แก้ไขประมวลฯ นั้นต่อท้ายไว้ และพระราชบัญญัติแต่ละฉบับก็นับมาตราของตัวเองเริ่มจาก 1 ถ้าปล่อยไว้อย่างนั้น การค้นหามาตรา 2 จะพบ 36 รายการ และมีเพียงรายการเดียวที่ถูก ตอนนี้ทุกรายการมีแฟล็กระบุว่าเป็นของตัวประมวลฯ เองหรือไม่ นี่คือเหตุที่ 2,254 รายการในไฟล์ประมวลกฎหมายแพ่งและพาณิชย์ เหลือเป็นบทบัญญัติของประมวลฯ 1,850 มาตรา ส่วนที่เหลือคือหัวข้อและพระราชบัญญัติที่ต่อท้าย</p>
      <p>ตัวอย่างอื่น ๆ:</p>
      <ul>
        <li>ประมวลกฎหมายที่ดินมีแปดรายการติดกันที่ป้ายเขียนว่ามาตรา 105 ทั้งหมด ความจริงคือมาตรา 105, 105 ทวิ, ตรี ไปจนถึงอัฏฐ ฟิลด์ป้ายตัดเลขมาตราให้สั้นลง จึงต้องแยกเลขมาตราจริงจากตัวข้อความ</li>
        <li>เลขลำดับคำเดียวกันสะกดได้สองแบบ คือ <code>อัฎฐ</code> ใช้ ฎ ในประมวลกฎหมายวิธีพิจารณาความแพ่ง และ <code>อัฏฐ</code> ใช้ ฏ ในประมวลกฎหมายที่ดิน</li>
        <li>เว็บไซต์ส่งคอมเมนต์ HTML ที่ถูก escape มา คือ <code>&amp;lt;!--[endif]--&amp;gt;</code> ซึ่งจะกลายเป็นคอมเมนต์ก็ต่อเมื่อ unescape แล้ว ตัวลบคอมเมนต์จึงมองไม่เห็น selftest ผ่านเพราะ fixture ใช้คอมเมนต์จริง มันถูกเขียนจากวิธีแก้ ไม่ใช่จากข้อมูล</li>
      </ul>
      <p>เมื่อแก้ทั้งหมดนี้แล้ว ไม่มีเลขมาตราใดในประมวลกฎหมายทั้งแปดฉบับที่ซ้ำกันอีก</p>
      <h3>เมื่อกฎหมายที่ถูกยกเลิกแล้วยังเป็น &#8220;ฉบับปัจจุบัน&#8221;</h3>
      <p>แต่ละฉบับในไทม์ไลน์ของกฎหมายมี <code>stateId</code> กำกับ <code>01</code> ดูเหมือนแปลว่า &#8220;มีผลใช้บังคับ&#8221; และ <code>00</code> ดูเหมือนแปลว่า &#8220;ถูกแทนที่แล้ว&#8221;</p>
      <p>ในแถวดัชนี 652 แถวที่ชื่อมีคำว่า <code>(ยกเลิก)</code> มีถึง 638 แถวที่อยู่ในสถานะ <code>01</code> ฉบับสุดท้ายของกฎหมายที่ถูกยกเลิกแล้ว ยังคงเป็นฉบับปัจจุบันของกฎหมายนั้น การยกเลิกจึงอ่านจากเครื่องหมายในชื่อ ไม่ใช่จากสถานะ</p>
      <p>การทดสอบเมื่อวันที่ 24 กันยายน พบสถานะที่สาม คือ <code>02</code> ประกาศแล้วแต่ยังไม่มีผลใช้บังคับ พร้อมประเภทฉบับที่ห้า คือฉบับปรับปรุงล่วงหน้าที่แสดงว่ากฎหมายจะเป็นอย่างไรเมื่อการแก้ไขที่รออยู่มีผล วันที่ 30 กันยายน เรื่องนี้ทำให้เจอกับดัก พระราชกฤษฎีกาว่าด้วยภาษีมูลค่าเพิ่มฉบับหนึ่งแสดงฉบับปรับปรุงล่วงหน้าที่มีผลตั้งแต่วันที่ 1 ตุลาคม 2026 และไม่ได้รวมพระราชกฤษฎีกาแก้ไขอีกฉบับที่ลงวันที่เดียวกัน ซึ่งคงอัตราที่ลดลงไว้อีกหนึ่งปี ถ้าอ่านฉบับล่วงหน้านั้นเพียงลำพัง จะได้อัตราที่ผิด ตอนนี้ห้องสมุดเก็บพระราชกฤษฎีกาแก้ไขฉบับนั้นแยกไว้ และติดธงเตือนฉบับล่วงหน้าใดก็ตามที่เหมือนฉบับปัจจุบัน</p>
      <p>ในวันเดียวกัน ห้องสมุดทั้งหมดได้รับการตรวจเทียบกับสำนักงานคณะกรรมการกฤษฎีกา 837 รายการในไทม์ไลน์ของกฎหมายแม่บทที่เก็บไว้ และกฎหมาย 773 ฉบับไม่มีฉบับใดเปลี่ยนแปลงนับจากวันที่ดึงมา แต่ตัวบทฉบับปรับปรุงเองอาจตามหลังการแก้ไขกฎหมาย ฉบับปรับปรุงล่าสุดของประมวลรัษฎากรบนเว็บไซต์ลงวันที่ 9 พฤศจิกายน 2021 เว็บไซต์ไม่สามารถแสดงการแก้ไขที่ยังไม่ได้นำมารวม หน่วยงานที่ออกกฎหมายจึงเป็นแหล่งตรวจสอบซ้ำ</p>
      <h3>ทำงานกับเอเจนต์ที่ผิดพลาดได้</h3>
      <p>เอเจนต์ที่รันโปรแกรมบนคอมพิวเตอร์ของผมและควบคุมเบราว์เซอร์ของผมได้ ทำงานได้มากในบ่ายวันเดียว แต่มันก็ผิดได้เกี่ยวกับไฟล์ที่มันไม่ได้เปิดดู และมันเขียนคำอธิบายที่อ่านลื่นไหลถึงสิ่งที่ไม่ได้เกิดขึ้นจริง กฎที่ผมใช้ทำงานล้วนเกิดจากเหตุการณ์จริง</p>
      <p>กฎเรื่องการแยกส่วนมาก่อน ห้องสมุดไทยอยู่ข้างห้องสมุดอินเดียบนดิสก์เดียวกัน และในช่วงแรก ข้อมูลสำรองของฝั่งไทยถูกเขียนลงในโฟลเดอร์สำรองของฝั่งอินเดีย ไม่มีอะไรสูญหาย แต่นับจากนั้น มีตัวป้องกันที่ปฏิเสธทุกเส้นทางที่อยู่ใต้โฟลเดอร์สำรองของอินเดีย และมีตัวตรวจที่ตรวจทุกสคริปต์ว่าเข้าถึงที่ใดได้บ้าง การสร้างตัวตรวจนี้ทำให้เกิดเหตุการณ์แปลก ๆ สองครั้งในเดือนนั้น:</p>
      <ul>
        <li><code>io.open(path, "w", newline=...)</code> ล้างไฟล์ก่อนที่จะแจ้งข้อผิดพลาดเมื่ออาร์กิวเมนต์ <code>newline</code> ไม่ถูกต้อง ตัวตรวจลบเนื้อหาตัวเองจนเหลือศูนย์ไบต์ด้วยวิธีนี้ ตอนนี้ทุกอย่างเขียนลงไฟล์ชั่วคราวก่อน แล้วจึงใช้ <code>os.replace</code> สลับเข้าที่</li>
        <li>เส้นทางแบบ Windows อย่าง <code>D:\...\Thai_Backup</code> สำหรับโปรแกรมบน Linux เป็นเพียงชื่อไฟล์ธรรมดา เมื่อสั่งจากฝั่งเอเจนต์ การสำรองข้อมูลจึงสร้างโฟลเดอร์ที่ชื่อเป็นเส้นทาง Windows ทั้งเส้นขึ้นมาในห้องสมุด ดูเผิน ๆ เหมือนการสำรองข้อมูลสำเร็จ</li>
      </ul>
      <p>กฎข้อที่สองคือทุกคำกล่าวอ้างต้องแสดงหลักฐาน ถ้าเอเจนต์บอกว่ามีไฟล์สำรองอยู่ มันต้องแสดงรายการไฟล์ ถ้ามันขอให้ผมรันไฟล์ใด มันต้องเปิดไฟล์นั้นก่อนและยกบรรทัดที่ทำงานจริงมาให้ดู กฎนี้เกิดหลังเซสชันหนึ่งที่มีข้อผิดพลาดแบบนี้ห้าครั้ง หนึ่งในนั้นคือคำแนะนำให้รันไฟล์ batch ที่จะทิ้งงานสามวันไป</p>
      <p>กฎข้อที่สามคือต้องสำรองข้อมูลก่อนทำอะไรก็ตามที่เขียนทับไฟล์ ครั้งหนึ่ง ไฟล์ batch ที่ถูกแนะนำมาแบบผ่าน ๆ ได้เขียนทับไฟล์ที่อีกสคริปต์หนึ่งบันทึกผล OCR ไว้ และเวลาประมวลผลของเครื่องหลายชั่วโมงก็หายไปด้วย ตอนนี้ต้องทำสำเนาก่อนเสมอ และต้องแสดงค่า checksum ทั้งสองค่า เมื่อวันที่ 1 ตุลาคม กฎนี้ถูกใช้กับเรื่องเล็ก ๆ ไฟล์แคชคำแปลไฟล์หนึ่งมีไบต์ NUL 1,443 ไบต์ที่หลงเหลือจากการทำงานที่หยุดกลางคันเมื่อวันที่ 29 กันยายน ไม่มีอะไรสูญหาย เพราะงานส่วนนั้นถูกทำใหม่แล้ว แต่ก็ทำสำเนาก่อน ลบบรรทัดที่เสีย และสแกนไฟล์แคชทั้ง 776 ไฟล์ก่อนสร้างดัชนีใหม่</p>
      <p>เอเจนต์ทำงานภายในขอบเขตที่ผมกำหนด มันใช้เฉพาะหน้าเว็บและ endpoint สาธารณะของเว็บไซต์ เคารพ robots.txt ไม่แก้ CAPTCHA ไม่กรอกรหัสผ่าน ไม่จัดการโทเคนสำหรับล็อกอิน และงานใดที่ผมต้องทำเอง จะมาเป็นขั้นตอนมีเลขกำกับตั้งแต่การคลิกครั้งแรก เมื่อมันบันทึกไฟล์จากหน้าเว็บ หน้าเว็บจะสร้างไฟล์ขึ้น แล้วการคลิกลิงก์จะบันทึกไฟล์ลงในโฟลเดอร์ Downloads ของผม</p>
      <h3>PDF ที่แสดงอย่างหนึ่งแต่บอกอีกอย่างหนึ่ง</h3>
      <p>ไม่ใช่ทุกอย่างที่มาทาง endpoint กฎกระทรวง บัญชีท้าย และแบบฟอร์ม มาในรูป PDF และ PDF อาจดูสมบูรณ์บนหน้าจอ ขณะที่ชั้นข้อความของมันบอกอีกอย่างหนึ่ง หน้ากระดาษวาดจากโครงร่าง glyph ในฟอนต์ที่ฝังไว้ ส่วนการดึงข้อความผ่านการจับคู่อีกชุดหนึ่งจากรหัสอักขระไปเป็น Unicode (ตาราง ToUnicode หรือ encoding และชื่อ glyph ของฟอนต์เมื่อไม่มีตาราง) ถ้าการจับคู่นั้นผิด หน้าจอจะถูก แต่ข้อความจะเป็นขยะ</p>
      <p>พบข้อผิดพลาดสามแบบ</p>
      <p>ตาราง glyph เสีย โครงร่างยังสมบูรณ์ มีแต่ตารางที่เสีย จึงแฮชโครงร่างของทุก glyph เรียนรู้การจับคู่แฮชกับตัวอักษรจากหน้าที่ตารางถูกต้อง แล้วค้นหา glyph ที่เสียด้วยรูปร่าง ใน 29 หน้าแรกที่เสีย ได้ 27,277 glyph จากรูปร่างที่ไม่ซ้ำกัน 88 รูป ถอดได้ทั้งหมด บั๊กหนึ่งที่ควรจำไว้ แคชฟอนต์ใช้ object id ของ PDF เป็นคีย์ และ object id ซ้ำกันข้ามไฟล์ได้</p>
      <p>Mac Thai เจ็ดไฟล์ถอดได้ 0% ผ่านตาราง glyph ทุกไฟล์ใช้ฟอนต์คู่เดียวกัน และชื่อ glyph ข้างในเป็นชื่อแบบ Mac Roman ที่วางอยู่ตามตำแหน่งของรหัส Mac OS Thai เก่าของ Apple ข้อความเป็นภาษาไทยที่เขียนด้วย Mac Thai แต่ถูกอ่านกลับเป็น Mac Roman วิธีซ่อมคือตาราง:</p>
      <pre><code># simplified; Python has no mac_thai codec
MAC_EXTRA = {0x83: "\u0E48", 0x88: "\u0E48", 0x89: "\u0E49", 0x8C: "\u0E4C",
             0x92: "\u0E31", 0x93: "\u0E47", 0x94: "\u0E34", 0x95: "\u0E35",
             0x97: "\u0E37", 0x8D: "\u201C", 0x8E: "\u201D"}

def macthai_decode(s):
    out = []
    for ch in s:
        try:
            b = ch.encode("mac_roman")[0]
        except UnicodeEncodeError:
            out.append("\uFFFD"); continue
        if b &lt; 0x80:
            out.append(ch)
        elif 0xA1 &lt;= b &lt;= 0xFB:            # Thai letters as in TIS-620; Apple's dashes and
                                           # symbols in this range differ and come out unknown
            out.append(bytes([b]).decode("cp874", errors="replace"))
        elif b in MAC_EXTRA:               # Apple's extra bytes, read from the pages
            out.append(MAC_EXTRA[b])
        else:
            out.append("\uFFFD")           # counted as unknown, never guessed
    return "".join(out)</code></pre>
      <p>ความพยายามครั้งแรกทำ ส หายไปทุกตัว ไบต์ 0xCA ใน Mac Roman คือช่องว่างไม่ตัดบรรทัด แต่ใน Mac Thai 0xCA คือ ส และครั้งแรกอ่านมันเป็นช่องว่าง การถอดรหัสที่ผิดก็อาจออกมาดูเหมือนภาษาไทยได้ หน้าที่ถอดแล้วจึงยอมรับเฉพาะเมื่อการสะกดแบบไทยถูกต้อง คือสระหรือวรรณยุกต์ต้องตามหลังพยัญชนะ สระหน้าต้องนำหน้าพยัญชนะ และอัตราการผิดต้องต่ำกว่า 1%</p>
      <p>สระอา ที่เขียนเป็นสระอำ PDF สิบไฟล์ 30 หน้า เขียน า ทุกตัวเป็น ำ และ ำ ตัวจริงทุกตัวมีช่องว่างนำหน้า เช่น <code>กำร</code> แทน <code>การ</code> และ <code>ก ำหนด</code> แทน <code>กำหนด</code> การตรวจหน้ากระดาษบอกว่าเป็นภาษาไทย เพราะเป็นตัวอักษรไทยจริง วิธีซ่อมแม่นยำ:</p>
      <pre><code># simplified
import re
MARK = "\uE000"
def fix_sara_aa(t):
    t = re.sub(r" ([\u0E48-\u0E4B]?)\u0E33", lambda m: m.group(1) + MARK, t)  # " ำ" is the real ำ
    return t.replace("\u0E33", "\u0E32").replace(MARK, "\u0E33")             # every other ำ is า</code></pre>
      <p>การตรวจจับใช้การสะกดที่ภาษาไทยไม่มี เช่น <code>กำร</code> <code>ตำม</code> <code>จำก</code> และอีกเล็กน้อย ที่ปรากฏอย่างน้อยสองครั้ง โดยไม่มีรูปที่ถูกต้องเลยในหน้านั้น ในโฟลเดอร์ PDF วิธีนี้เลือกได้ตรง 30 หน้านั้นพอดี หนึ่งในสิบไฟล์คือกฎกระทรวงตามประมวลกฎหมายที่ดินว่าด้วยค่าธรรมเนียมจดทะเบียนการโอนและการจำนอง</p>
      <p>จากนั้นก็สามารถตรวจเทียบกับแหล่งทางการได้ กฎกระทรวงและพระราชกฤษฎีกา 73 ฉบับภายใต้พระราชบัญญัติหลักได้ตัวบททางการกลับมาผ่าน endpoint โดยมีรหัสเดียวกับ PDF ที่อ่านไปแล้ว ตัววัดคือสัดส่วนของตัวอักษรไทยในแต่ละหน้า PDF ที่พบเรียงตามลำดับในตัวบททางการ:</p>
      <pre><code>method     pages   median
macthai      24    0.984
glyph        31    0.978
pua          38    0.978
layer       108    0.942
ocr         160    0.717</code></pre>
      <p><code>layer</code> คือชั้นข้อความที่ใช้ตามที่เป็น <code>pua</code> คือชั้นข้อความที่ใช้พื้นที่ private-use ของ Unicode ทุกหน้าที่ได้ต่ำกว่า 0.8 ซึ่งไม่ใช่หน้าที่มีปัญหาสระอา เป็นแบบฟอร์มหรือภาคผนวก ตัวบททางการมีเฉพาะบทบัญญัติที่ใช้บังคับ และ PDF เป็นแหล่งเดียวของแบบฟอร์มและบัญชีท้าย ค่ามัธยฐาน 0.717 ของ Tesseract กับภาษาไทยที่สแกนมา คือเหตุผลที่ข้อความจาก OCR อยู่ในระดับ T4 ตัวเลขไทยแย่ที่สุด</p>
      <h3>ภาษาอังกฤษจากเครื่องที่ผมจะไม่ยกอ้าง</h3>
      <p>ตัวบทไทยที่ถูกต้องแต่ผมอ่านไม่ออกมีประโยชน์กับผมไม่มากนัก ทุกมาตราจึงมีคำแปลภาษาอังกฤษด้วยเครื่อง ทำบนเครื่องของผมเองด้วยโมเดลเปิด Gemma ของ Google (<code>gemma3:27b</code>) ผ่าน Ollama บนการ์ดจอ RTX 5090 มาตรา 26,978 มาตราถูกแบ่งเป็น 27,198 ชิ้นเพื่อแปล และบันทึกการทำงานแสดงเวลาประมวลผลของโมเดลราว 36 ชั่วโมง ระหว่างค่ำวันที่ 28 กันยายน ถึงบ่ายวันที่ 30 กันยายน</p>
      <p>ทั้งหมดอยู่ในระดับ T4 การตรวจสอบมีไว้ให้มันดีพอสำหรับใช้ค้นหาและอ่านเคียงข้างภาษาไทย</p>
      <ol>
        <li>ปฏิเสธตั้งแต่ตอนแปล: คำตอบจะถูกปฏิเสธถ้าว่างเปล่า เป็นภาษาไทยเสียส่วนใหญ่ หรือสั้นหรือยาวเกินไปมากเมื่อเทียบกับภาษาไทยต้นทาง</li>
        <li>การตรวจด้วยโปรแกรมกับทุกชิ้นหลังการแปลทุกรอบ: ตัวเลขในภาษาไทยที่หายไปจากภาษาอังกฤษ เลขมาตราที่ไม่ตรงกัน มาตราที่มีคำต่อท้าย (<code>มาตรา ๓ ตรี</code>) ที่ไม่ได้แปลเป็น &#8220;3 ter&#8221; จำนวนรายการที่น้อยกว่าภาษาไทย ข้อความวนซ้ำ และคำพูดเกินเลยอย่าง &#8220;Here is the translation&#8221; ชิ้นที่ไม่ผ่านถูกส่งกลับไปพร้อมเหตุผลที่เขียนไว้ในคำสั่งแปลใหม่ ไม่เกินสองครั้ง หลังจากนั้นจะถูกจดไว้ให้อ่านตรวจ</li>
        <li>แผ่นตัวอย่าง 30 ชิ้นสุ่ม ภาษาไทยเคียงภาษาอังกฤษ ท้ายรายงานทุกฉบับ</li>
        <li>ตารางการใช้ศัพท์: สำหรับศัพท์กฎหมายแต่ละคำที่กำหนดไว้ ภาษาอังกฤษใช้คำแปลที่ตกลงไว้บ่อยแค่ไหน และศัพท์นั้นปรากฏในบริบทภาษาไทยแบบใด</li>
      </ol>
      <p>ข้อที่สี่จับความผิดพลาดที่ดีที่สุดของเดือนได้ รายการศัพท์กำหนดว่า <code>อาศัย</code> แปลว่า &#8220;habitation&#8221; อย่างในคำว่าสิทธิอาศัย แต่มีเพียง 24% ของชิ้นงานที่ทำตาม และบริบทที่พบบ่อยที่สุดคือ <code>อาศัยอำนาจ</code> ซึ่งแปลว่า &#8220;by virtue of the power&#8221; คำนี้เมื่ออยู่ในคำอื่นถูกแปลเป็น habitation มาตรา 96 ทวิ แห่งประมวลกฎหมายที่ดินออกมาเป็นคนต่างด้าวได้ที่ดิน &#8220;through habitation based on a treaty&#8221; ขณะที่ภาษาไทยหมายถึงโดยอาศัยบทสนธิสัญญา ศัพท์จึงถูกจำกัดให้แคบลงเป็น <code>สิทธิอาศัย</code> = right of habitation และคำแปลที่ผิดลดลงจาก 202 เหลือ 6 ซึ่งทั้ง 6 เป็นการใช้ที่ถูกต้อง</p>
      <p>ชิ้นที่ถูกติดธงลดลงจาก 83 ในรอบแรก เหลือ 7 ในรอบสุดท้าย Claude อ่านทั้ง 7 ชิ้นเทียบกับภาษาไทย หกชิ้นเป็นการตกหล่นของตัวเลขหรือการอ้างอิงมาตราจริง และถูกบันทึกไว้เป็นข้อบกพร่องที่ทราบแล้ว อีกหนึ่งชิ้นเป็นการพิมพ์ผิดในต้นฉบับ นี่คือเครื่องตรวจเครื่อง ซึ่งเป็นเหตุผลที่ไม่มีส่วนใดของมันถูกยกอ้างเลย</p>
      <h3>ไฟล์เดียว เสี้ยววินาที</h3>
      <p>ห้องสมุดฝั่งอินเดียใช้ไฟล์ parquet กับ DuckDB เพราะคำพิพากษาหนึ่งล้านฉบับต้องการเช่นนั้น แห่งนี้เป็นไฟล์ SQLite ไฟล์เดียวที่มีดัชนี FTS5 สองชุด</p>
      <ul>
        <li>ภาษาไทยใช้ tokenizer แบบ <code>trigram</code> (SQLite 3.34 ขึ้นไป) ภาษาไทยเขียนโดยไม่เว้นวรรคระหว่างคำ และการจับคู่แบบ trigram ไม่ต้องตัดคำ คำค้นที่สั้นกว่าสาม code point ใช้ดัชนี trigram ไม่ได้ จึงถอยไปใช้ <code>LIKE</code> สระและวรรณยุกต์ไทยเป็น code point แยกกัน ดังนั้น ที่ จึงมีสาม code point</li>
        <li>ภาษาอังกฤษใช้ Porter stemming กับการจับคู่คำนำหน้า รายการคำพ้องสั้น ๆ (sale/sell, land/immovable, wife/spouse, foreigner/alien) และรายการศัพท์ วลีภาษาอังกฤษที่ตรงกับคำแปลในรายการศัพท์จะค้นหาศัพท์ภาษาไทยด้วย โดยให้น้ำหนักครึ่งหนึ่ง</li>
      </ul>
      <p>SQLite เขียนไฟล์ฐานข้อมูลลงในโฟลเดอร์ตามที่สภาพแวดล้อมของเอเจนต์ mount ไว้ไม่ได้ ความพยายามครั้งแรกล้มเหลวด้วย <code>disk I/O error</code> ดัชนีจึงถูกสร้างในหน่วยความจำ แล้วเขียนออกมาครั้งเดียว:</p>
      <pre><code># simplified; serialize() needs Python 3.11+
import os, sqlite3
mem = sqlite3.connect(":memory:")
mem.execute("CREATE VIRTUAL TABLE fts_th USING fts5(text_th, tokenize='trigram')")
# ... load sections, agency texts, notes ...
with open(tmp_path, "wb") as f:
    f.write(mem.serialize())
os.replace(tmp_path, index_path)   # the old index survives until the new one is complete</code></pre>
      <p>การค้นหาหน้าตาเป็นแบบนี้ วงเล็บเหลี่ยมคือคำที่ค้นเจอ:</p>
      <pre><code>python3 th_ask.py "matrimonial property"

Civil and Commercial Code, section 1474
  TH  [T1 Thai, Council of State consolidation, in force from 2025-03-25]
      มาตรา ๑๔๗๔ สินสมรสได้แก่ทรัพย์สิน (๑) ที่คู่สมรสได้มาระหว่างสมรส ...
  EN  [T4 machine translation - NEVER quote or cite]
      Section 1474 [Matrimonial] [property] consists of [property] (1) which
      the spouses obtained during marriage. ...</code></pre>
      <p>ณ วันที่ 1 ตุลาคม 2026: 26,978 มาตรา ทุกมาตรามีคำแปลภาษาอังกฤษด้วยเครื่อง หน้าภาษาอังกฤษจากบุคคลภายนอก 1,218 หน้า และข้อความจากหน่วยงาน ภาษี OCR สนธิสัญญา และบันทึก ราว 8,400 ส่วน ดัชนีมีขนาด 146 MB สร้างใหม่ทั้งหมดในราว 11 วินาที และการค้นหาใช้เวลา 0.1 ถึง 0.2 วินาที ผมพิจารณาจะย้ายไปใช้โครงสร้าง parquet และ DuckDB แบบห้องสมุดอินเดีย แล้วก็คงไว้อย่างเดิม โครงสร้างนั้นมีไว้สำหรับดัชนีคำพิพากษาขนาด 5.8 GB การสร้างใหม่ที่ใช้เวลา 11 วินาทีไม่ได้ประโยชน์อะไรจากมัน</p>
      <h3>คำพิพากษา ไว้ทีหลัง</h3>
      <p>คำพิพากษาศาลฎีกาของไทยยังไม่อยู่ในห้องสมุดนี้ นี่เป็นการเลือกลำดับงาน ในระบบซีวิลลอว์ คำพิพากษาศาลฎีกาไม่ใช่บรรทัดฐานที่มีผลผูกพัน แต่มีน้ำหนักในการโน้มน้าวและช่วยในการตีความประมวลกฎหมาย ตัวบทกฎหมายจึงต้องมาก่อน คำพิพากษาอยู่ในรายการงานภายหลัง โดยจะเลือกเฉพาะคำพิพากษาเรื่องทรัพย์สิน ครอบครัว และมรดก ไม่ใช่ดาวน์โหลดทั้งหมด</p>
      <h3>สิ่งที่ผมตรวจสอบก่อนจะพึ่งพา</h3>
      <ul>
        <li>ภาษาอังกฤษจากเครื่อง ใช้เพื่อค้นหาและอ่าน สิ่งใดที่จะนำไปใช้ดำเนินการ ต้องมีตัวบทภาษาอังกฤษที่เป็นทางการถ้ามี หรือต้องมีผู้ที่อ่านภาษาไทยได้อ่านตัวบทภาษาไทย</li>
        <li>ตัวบทฉบับปรับปรุง อาจตามหลังการแก้ไขกฎหมาย หน่วยงานที่ออกกฎหมายคือแหล่งตรวจสอบซ้ำ</li>
        <li>ข้อความจาก OCR ใช้หาหน้าที่ต้องการ แล้วจึงอ่านหน้านั้น</li>
        <li>แบบฟอร์มและบัญชีท้าย มีอยู่ใน PDF เท่านั้น และ PDF บางไฟล์ยังไม่ได้อ่าน</li>
        <li>PDF ของธนาคารแห่งประเทศไทย มี 1,821 หน้าที่ชั้นข้อความเสีย วัดแล้วแต่ยังแก้ไม่ได้ วิธี glyph ที่ปรับปรุงแล้วแฮช glyph ได้ 98% แต่มากกว่าครึ่งของ glyph เหล่านั้นไม่เคยปรากฏในหน้าที่สมบูรณ์ให้เรียนรู้</li>
      </ul>
      <h3>ทำไปเพื่ออะไร</h3>
      <p>ผมต้องการเริ่มต้นคำถามทางกฎหมายไทยใดก็ตามที่เกี่ยวกับเรื่องของผมเองจากตัวบทภาษาไทย โดยภาษาอังกฤษทุกคำที่ผมพึ่งพามีป้ายบอกที่มาอย่างตรงไปตรงมา ตอนนี้ห้องสมุดทำได้แล้ว การค้นหาให้มาตราภาษาไทย คำแปลระดับ T4 ที่บอกว่ามาตรานั้นว่าอย่างไร และแหล่งทางการให้ไปตรวจยืนยัน</p>
      <h3>หมายเหตุ: สิ่งที่บทความนี้ไม่ใช่</h3>
      <p>ผมเป็นทนายความที่จดทะเบียนในอินเดีย ผมไม่ใช่ทนายความไทย ผมไม่ได้ประกอบวิชาชีพกฎหมายไทย และในฐานะคนต่างชาติ ผมก็ทำไม่ได้ กฎหมายไทยให้เฉพาะผู้มีสัญชาติไทยจดทะเบียนเป็นทนายความได้ (พระราชบัญญัติทนายความ พ.ศ. 2528 มาตรา 35) และงานให้บริการทางกฎหมายหรืออรรถคดีเป็นงานที่กฎหมายไทยสงวนไว้สำหรับคนไทย โดยมีข้อยกเว้นแคบ ๆ สำหรับงานอนุญาโตตุลาการ ห้องสมุดนี้เป็นเครื่องมือค้นคว้าส่วนตัวสำหรับเรื่องของผมเอง ไม่มีส่วนใดในบทความนี้เป็นคำแนะนำเกี่ยวกับกฎหมายไทย สำหรับเรื่องกฎหมายไทยใด ๆ โปรดปรึกษาทนายความที่ได้รับใบอนุญาตในประเทศไทย</p>
      <p>ตัวบทในห้องสมุดเป็นเอกสารสาธารณะ กฎหมาย ระเบียบ ประกาศ คำสั่ง และคำพิพากษาของไทย รวมทั้งคำแปลที่หน่วยงานรัฐของไทยจัดทำ ไม่ถือเป็นงานอันมีลิขสิทธิ์ตามพระราชบัญญัติลิขสิทธิ์ (มาตรา 7) ภาษาอังกฤษจากบุคคลภายนอกเก็บไว้เพื่อการค้นคว้าส่วนตัวเท่านั้น และไม่ได้นำมาเผยแพร่ในที่นี้</p>
      <h3>อภิธานศัพท์</h3>
      <p><strong>พ.ศ.</strong>: พุทธศักราช ไทยนับปีตามนี้ พ.ศ. = ค.ศ. + 543 ดังนั้น พ.ศ. 2569 คือ ค.ศ. 2026</p>
      <p><strong>สำนักงานคณะกรรมการกฤษฎีกา (ocs.go.th)</strong>: หน่วยงานยกร่างกฎหมายของรัฐบาลไทย ผู้เผยแพร่ตัวบทกฎหมายไทยฉบับปรับปรุง</p>
      <p><strong>ฉบับปรับปรุง (consolidation)</strong>: ตัวบทกฎหมายที่รวมการแก้ไขเพิ่มเติมไว้แล้ว ใช้เพื่อความสะดวก ตัวกฎหมายจริงคือสิ่งที่ประกาศในราชกิจจานุเบกษา</p>
      <p><strong>ราชกิจจานุเบกษา</strong>: หนังสือราชการที่ใช้ประกาศกฎหมายไทย กฎหมายเริ่มมีผลนับจากการประกาศนี้</p>
      <p><strong>ทวิ ตรี จัตวา</strong>: มาตราที่แทรกเพิ่มต่อจากมาตราเดิม เทียบได้กับ bis, ter, quater</p>
      <p><strong>รายการศัพท์ (termbase)</strong>: รายการศัพท์กฎหมายไทยพร้อมคำแปลภาษาอังกฤษที่ตกลงกันไว้ ใช้ให้คำแปลด้วยเครื่องสม่ำเสมอ</p>
      <p><strong>FTS5</strong>: ส่วนขยายการค้นหาข้อความเต็มของ SQLite <strong>Trigram tokenizer</strong>: สร้างดัชนีจากทุกช่วงสามตัวอักษร ทำให้ค้นหาข้อความที่ไม่มีการแบ่งคำได้โดยไม่ต้องตัดคำ</p>
      <p><strong>ตาราง ToUnicode</strong>: ตารางใน PDF ที่บอกซอฟต์แวร์ว่ารหัสอักขระแต่ละตัวคือตัวอักษร Unicode ใด ถ้าตารางผิด หน้าจะดูถูกต้องแต่ดึงข้อความออกมาเป็นขยะ</p>
      <p><strong>Mac Thai</strong>: รหัสอักขระภาษาไทยแบบ 8 บิตรุ่นเก่าของ Apple ตัวอักษรใกล้เคียง TIS-620 โดยมีไบต์เพิ่มสำหรับวรรณยุกต์และสระ</p>
      <p><strong>OCR</strong>: ซอฟต์แวร์รู้จำตัวอักษร Tesseract คือเครื่องมือ OCR แบบโอเพนซอร์สที่ใช้ในงานนี้</p>
    </div>

  </div>
</div>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://techblog.viveksanghi.com/2026/10/thai-law-corpus/">The law I cannot read · กฎหมายที่ผมอ่านไม่ออก</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techblog.viveksanghi.com/2026/10/thai-law-corpus/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">265</post-id>	</item>
		<item>
		<title>The section that wasn’t there</title>
		<link>https://techblog.viveksanghi.com/2026/09/building-a-local-indian-law-corpus/</link>
					<comments>https://techblog.viveksanghi.com/2026/09/building-a-local-indian-law-corpus/#respond</comments>
		
		<dc:creator><![CDATA[Vivek Sanghi]]></dc:creator>
		<pubDate>Fri, 04 Sep 2026 11:30:00 +0000</pubDate>
				<category><![CDATA[legal tech]]></category>
		<category><![CDATA[software]]></category>
		<category><![CDATA[sysadmin]]></category>
		<category><![CDATA[ai assisted]]></category>
		<category><![CDATA[data quality]]></category>
		<category><![CDATA[duckdb]]></category>
		<category><![CDATA[indian law]]></category>
		<category><![CDATA[legal research]]></category>
		<category><![CDATA[ocr]]></category>
		<category><![CDATA[parquet]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[search]]></category>
		<category><![CDATA[stubborn fanatic]]></category>
		<category><![CDATA[telangana]]></category>
		<guid isPermaLink="false">https://techblog.viveksanghi.com/?p=261</guid>

					<description><![CDATA[<p>I asked my own law database for section 5 of the HMDA Act and it said there was no such section. There is. Building a searchable corpus of Indian statutes and judgments on my own machine, and what auditing it taught me about trusting legal text.</p>
<p>The post <a href="https://techblog.viveksanghi.com/2026/09/building-a-local-indian-law-corpus/">The section that wasn&#8217;t there</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I asked my own law database for section 5 of the Hyderabad Metropolitan Development Authority Act, 2008. It told me there was no such section.</p>



<p class="wp-block-paragraph">There is. It is on the printed page in front of me. The database had the text, all of it, and could not find it, because the text was filed under a different number.</p>



<p class="wp-block-paragraph">This is a post about building a searchable corpus of Indian statutes and case law on my own machine, and about the fact that the dangerous failure is never the database that says nothing. It is the one that answers with confidence and is wrong.</p>



<p class="wp-block-paragraph">I built it over several weeks with Claude. I made the decisions, ran everything on my own hardware, and checked the law myself; the scripts and most of the analysis came from the model. The mistakes are in here as well, because on a project of this shape the mistakes are the content. Nearly every genuine improvement came from something being printed to screen that looked wrong.</p>



<h2 class="wp-block-heading">Why hold law locally at all</h2>



<p class="wp-block-paragraph">I am an advocate in Hyderabad. My practice is property, title due diligence, drafting. The work needs the text of statutes, rules, government orders and judgments, constantly, and mostly for a narrow set of jurisdictions: Central Acts, Telangana, and the Andhra Pradesh that Telangana inherited.</p>



<p class="wp-block-paragraph">The obvious answer is a subscription database. They work. But two things pushed me the other way.</p>



<p class="wp-block-paragraph">The first is cost and dependence. The second matters more: I cannot audit what I cannot see inside. When a subscription database returns nothing for a section, I have no way of knowing whether the section does not exist, or whether their ingestion mangled it the way mine mangled HMDA section 5. When my own corpus does that, I can go and look.</p>



<p class="wp-block-paragraph">There is also a speed argument that turns out to be real. Searching a million judgments on my own machine takes about a second.</p>



<h2 class="wp-block-heading">What the corpus is made of</h2>



<p class="wp-block-paragraph">The whole thing lives at <code>D:AiTools</code> on a Windows workstation, a Ryzen 9950X3D with 64 GB of RAM. The pipeline is deliberately dull: scrape original sources, convert to markdown, write parquet, query with DuckDB. No database server, no index server, nothing to keep running. Files on a disk.</p>



<p class="wp-block-paragraph">An early decision shaped everything after it. There is a bulk dataset of Indian legislation on Hugging Face, and I downloaded it. Then I decided not to use it for statutes, and to scrape India Code and the Telangana Law Department fresh instead. Not because the dataset is bad, but because I wanted to know the provenance of every section I might cite, and to be able to go back to the page it came from. I kept the dataset to cross-check against, once.</p>



<p class="wp-block-paragraph">What the Act surface holds today, counted this morning:</p>



<pre class="wp-block-code"><code>58,381 rows across eight parquet files

  India Code       828 Central Acts     35,390 sections
                   112 Telangana Acts    1,542 sections
  Telangana Law Dept   184 Acts
  Schedules            380 Acts          1,484 schedule rows
  Act-level PDFs       452 Acts
      196  Andhra Pradesh, pre-2014, applies in Telangana
      179  Telangana
       56  Andhra Pradesh, post-2014, residuary AP only
       18  Central
  Harvested G.O.s, circulars, orders   757 rows</code></pre>



<p class="wp-block-paragraph">Separately there is an index of my own prior work: <strong>18,932 documents</strong> out of my OneDrive, read-only, so a search can turn up the deed I drafted in 2019 alongside the section it relied on.</p>



<p class="wp-block-paragraph">That AP split is not decorative. Andhra Pradesh was bifurcated on 2 June 2014, and an AP Act from before that date still applies in Telangana while one from after it does not. Every result line says which, because an Act that does not apply is still an Act, and the distinction has to survive into whatever is filed.</p>



<h2 class="wp-block-heading">Counting is not checking</h2>



<p class="wp-block-paragraph">Here is the mistake that reshaped how I check this corpus.</p>



<p class="wp-block-paragraph">The corpus held the HMDA Act with 58 sections, numbered up to 58. That looks complete. It is not. Sections 5, 6 and 8 were absent, and their text was inside the row filed as section 9, a row that opens with the repeal of a 1988 water-rates ordinance and runs to 10,362 characters, having swallowed several sections that come before it.</p>



<p class="wp-block-paragraph">The cause is visible on the printed page. HMDA prints its section headings as marginal notes, in a column beside the body text. Extract a text layer from that PDF and the headings interleave with the sentences, so <code>5.</code> never begins a line, and a splitter looking for a number at the start of a line never sees one.</p>



<p class="wp-block-paragraph">So I stopped trusting counts and audited numbering continuity instead: for every Act holding eight or more numbered sections, is the run of numbers unbroken?</p>



<p class="wp-block-paragraph">When that ran, on 27 August 2026, it found <strong>1,151 Acts</strong> with eight or more sections and <strong>99 with a gap</strong>. Most gaps are honest: sections repealed, or omitted from the source. But in 38 of them the missing section&#8217;s heading was present <em>inside another row</em>. The text was in the corpus, filed under the wrong number.</p>



<p class="wp-block-paragraph">Re-running the same audit today gives 1,158 Acts and <strong>86 gaps</strong>. The difference is the repair described below.</p>



<p class="wp-block-paragraph">The consequence is worth stating plainly. Full-text search reaches misfiled text without difficulty. It is a lookup by section number that fails, and it fails by reporting that a section does not exist when it does. That is the wrong direction to fail in.</p>



<h2 class="wp-block-heading">repair.py, and five ways it nearly filed the wrong text</h2>



<p class="wp-block-paragraph">Recovering misfiled sections sounds simple: find the missing number used as a heading inside another row, cut from there to the next heading, file it under its own number.</p>



<p class="wp-block-paragraph">It is not simple, and the first version was wrong in five separate ways. All five were found by running it in a sandbox and reading the output before it touched anything.</p>



<p class="wp-block-paragraph"><strong>It missed the section it was written for.</strong> The heading pattern required text to continue on the same line. India Code prints <code>5.</code> alone, with <code>(1) There shall be...</code> on the line below. HMDA section 5, the whole reason the script existed, did not match.</p>



<p class="wp-block-paragraph"><strong>Cuts ran to the end of the host row.</strong> A recovered section 8 swallowed section 9 and everything after it. Cuts now end at the next heading of any number.</p>



<p class="wp-block-paragraph"><strong>Footnotes were recovered as sections.</strong> <code>54. Section 43 substituted by Act No.11 of 1969</code> is a footnote. It looks exactly like a section heading.</p>



<p class="wp-block-paragraph"><strong>Page-group rows were used as hosts.</strong> Where an Act could not be split reliably it is stored as page groups, which contain the entire text, so every number appears in them. Recovering from those produces nonsense.</p>



<p class="wp-block-paragraph"><strong>The same number was recovered from six different rows</strong> of the Wealth-tax Act.</p>



<p class="wp-block-paragraph">The gate that did most of the work is a structural one: a row can only have swallowed sections that <em>precede</em> it. So a recovered number must be lower than its host&#8217;s number and within 40 of it. Without that gate, the Greater Hyderabad Municipal Corporation Act &#8220;recovered&#8221; section 19 out of a row filed as section 687, which is a schedule of cycle-rickshaw fees.</p>



<p class="wp-block-paragraph">Ungated, the pass proposed 34 Acts and 133 sections. Gated, it proposed 23 Acts and 49 sections, and the 23 rejected candidates each carry a stated reason.</p>



<p class="wp-block-paragraph">Today <code>repairs_acts.parquet</code> holds <strong>49 recovered sections across 23 Acts</strong>, each labelled with the row it was cut from and marked medium confidence. It never edits another script&#8217;s file. The recovered rows join the same glob the reader already reads.</p>



<h2 class="wp-block-heading">A correction</h2>



<p class="wp-block-paragraph">An earlier note of mine recorded that the GHMC Act had &#8220;37 of 53 gaps recoverable&#8221;. That was wrong, and it came from a crude heading search with none of the gates above.</p>



<p class="wp-block-paragraph">A properly gated pass recovers <strong>nothing</strong> from GHMC. Its numbering gaps are footnote markers and schedule item numbers, not missing sections. Today the audit still shows GHMC with 635 sections present and 52 numbers missing, and that is the correct answer: they were never sections.</p>



<p class="wp-block-paragraph">I am recording the wrong figure as well as the right one because a corpus you cannot correct in public is a corpus nobody should trust, including you.</p>



<h2 class="wp-block-heading">Heuristics that fail interestingly</h2>



<p class="wp-block-paragraph">Three gates keep schedules and tables out of the recovery pass, and each was calibrated against real documents.</p>



<p class="wp-block-paragraph"><strong>Digit density.</strong> Statutory prose runs 3 to 4 per cent digits. Schedules run 24 per cent and up. The threshold sits at 18. This caught a recovered &#8220;section&#8221; of <strong>247,454 characters</strong> that was the Telangana Panchayat Raj Act&#8217;s schedule of village names.</p>



<p class="wp-block-paragraph"><strong>Line length.</strong> Text laid out as a table puts each field on its own line. Village schedules average 9 characters per line; statutory prose averages 75.</p>



<p class="wp-block-paragraph">That second gate was first set at 28 characters, and it rejected HMDA sections 5 and 8. India Code wraps its prose at about 27, and a section that is a list of committee members has short lines. A threshold tuned on the wrong sample rejected precisely the sections the whole exercise existed to recover. Moved to 15, the pass went from 44 sections to 49, the five additions being HMDA sections 5 and 8, Telangana Municipalities section 32, and two sections of the Telangana State Private Universities Act.</p>



<p class="wp-block-paragraph"><strong>Capture ratio.</strong> Any split that captures under 92 per cent of a document&#8217;s text is rejected in favour of page chunks. This exists because of a specific failure: an Act splitter applied to a Government Order treats the numbered list of earlier G.O.s it cites as the sections. G.O.Ms.No.59 came out as a single &#8220;section 4&#8221; holding 47 per cent of the document, with the abstract and the operative order silently gone. Instruments that are not Acts, Rules or Regulations are no longer split at all.</p>



<h2 class="wp-block-heading">Fifty gigabytes of judgments, and what they do not carry</h2>



<p class="wp-block-paragraph">Judgments come from the open-india-law dataset: <strong>63 parquet files, 53.7 GB</strong>. I hold all of them.</p>



<p class="wp-block-paragraph">The indexed corpus covers the courts that matter to my practice:</p>



<pre class="wp-block-code"><code>  telangana        1,005,147 judgments
  andhra-pradesh     261,778
  supreme-court       37,014</code></pre>



<p class="wp-block-paragraph">Now the part that decides how you use this data. Metadata completeness, measured this morning:</p>



<pre class="wp-block-code"><code>  court            judgments    no citation   no judges
  telangana        1,005,147        100.0%       81.9%
  andhra-pradesh     261,778        100.0%       88.3%
  supreme-court       37,014          0.0%        0.0%</code></pre>



<p class="wp-block-paragraph">Every Supreme Court judgment carries a citation and a bench. <strong>Not one High Court judgment in over 1.26 million carries a citation.</strong> You get the text and the case number and nothing you can put in a plaint.</p>



<p class="wp-block-paragraph">That is not a defect in the dataset so much as a fact about what is freely available, and it changes the workflow rather than ending it. The corpus is where I find the case and read the reasoning. The citation comes from elsewhere, and it comes before anything is filed.</p>



<p class="wp-block-paragraph">The dates need care too. Telangana decision dates in this data run from 23 October 1952 to <strong>27 December 2079</strong>. Twelve judgments are dated in the future. The <code>year</code> column in the Andhra Pradesh file tracks ingestion rather than decision and cannot be used for anything.</p>



<h2 class="wp-block-heading">Which court binds</h2>



<p class="wp-block-paragraph">For a matter in Telangana, a Supreme Court judgment binds, the Telangana High Court binds, and the Andhra Pradesh High Court binds only for the period before bifurcation. Bombay and Madras are persuasive, and worth reading, and they are not the same thing. The distinction has to survive into the document.</p>



<p class="wp-block-paragraph">The dataset splits along that line almost exactly, which was a pleasant surprise:</p>



<pre class="wp-block-code"><code>  telangana, decided before 01.01.2019          707,308
  andhra-pradesh, decided before 01.01.2019       1,011
  andhra-pradesh, decided 01.01.2019 onwards    259,200</code></pre>



<p class="wp-block-paragraph">The <code>in_telangana</code> file is the predecessor court&#8217;s whole corpus going back to 1952. The <code>in_andhra-pradesh</code> file is overwhelmingly the reconstituted court at Amaravati. So the default tier applies a date predicate to the AP file alone, and the binding tier comes to <strong>1,043,172 judgments</strong>. Every result line is labelled BINDING or persuasive, and a binding search says on its face that the other High Courts were not searched.</p>



<p class="wp-block-paragraph">This nearly went wrong in a way worth describing, because it is the most dangerous class of bug in the whole project.</p>



<p class="wp-block-paragraph">DuckDB&#8217;s <code>regexp_extract(string, pattern)</code> returns the <strong>whole match</strong>, not the capture group. The build stored <code>in_telangana_judgments</code> where it meant <code>telangana</code>. Every comparison against a bare court name then failed silently. The effect was that <strong>260,766 post-2019 Amaravati judgments sat in the binding tier, labelled BINDING</strong>, and the authority ranking collapsed to plain date order.</p>



<p class="wp-block-paragraph">Nothing errored. Nothing looked broken. A one-argument mistake quietly relabelled persuasive authority as binding, which in a document going to court is exactly the sort of error you do not get to explain away. The build now passes the group index, and the reader normalises whatever form it finds, so a stale index cannot mislabel authority even if it is rebuilt wrongly.</p>



<h2 class="wp-block-heading">Search without a search engine</h2>



<p class="wp-block-paragraph">The obvious way to search a million judgments is a full-text index. I could not use one, for a reason that is specific and worth explaining, because it shaped the design.</p>



<p class="wp-block-paragraph">The environment Claude reaches my machine through is a small sandbox: 2 CPUs, 3 GB of RAM. My workstation reports 32 CPUs. A full scan of the 9.1 GB binding tier takes seconds on the workstation and over three minutes in the sandbox, which is past the timeout on a single call. DuckDB&#8217;s full-text extension cannot be installed in that sandbox at all: the proxy variable is malformed for its parser, and with the proxy unset there is no network.</p>



<p class="wp-block-paragraph">So an index that needed the extension would have been unreadable by the one reader that most needed it. The answer was plain sorted parquet, built natively on the fast machine, read cheaply by anything.</p>



<p class="wp-block-paragraph">Four files:</p>



<pre class="wp-block-code"><code>  cases.parquet      one row per judgment            0.05 GB
  text.parquet       chunks, sorted by case          4.24 GB
  vocab.parquet      words and their ids             0.10 GB
  postings.parquet   (token, case, chunk)            0.91 GB
                                            total    5.31 GB</code></pre>



<p class="wp-block-paragraph">Over <strong>1,303,939 judgments</strong>, <strong>15,057,895 distinct indexed words</strong>, and <strong>397,445,969 postings</strong>.</p>



<p class="wp-block-paragraph">Two calibration decisions matter more than the structure.</p>



<p class="wp-block-paragraph"><strong>Minimum word length is 4, not 5.</strong> Sale, deed, gift, rent and lien all carry legal phrases. Three-letter words are lost, so <em>lis pendens</em> is found on <code>pendens</code> alone, which is fine because the rarer half of a phrase does the work.</p>



<p class="wp-block-paragraph"><strong>The commonness cut is 10 per cent, not 1.</strong> Words appearing in more than that share of cases are not indexed, because indexing them costs enormously and narrows nothing. The first threshold chosen was 1 per cent. Measured against 17,684 Supreme Court cases:</p>



<pre class="wp-block-code"><code>  possession      5.3%
  registration    1.9%
  performance     1.8%
  adverse         1.6%
  injunction      1.1%
  mortgage        1.0%</code></pre>



<p class="wp-block-paragraph">A 1 per cent cut would have excluded <strong>every one of them</strong>. It would have produced a technically excellent index, of a corpus of property judgments, that could not search for possession, registration, performance, adverse or mortgage. Only 375 words are common enough to exclude at 10 per cent, and when a search hits one it is reported by name, so a search for &#8220;part performance&#8221; says it searched on <code>performance</code> alone.</p>



<p class="wp-block-paragraph">Matching is by <strong>proximity</strong>, not merely co-occurrence. Postings carry the chunk as well as the case, so intersecting on chunk requires the words to fall within the same passage of roughly 2,000 characters. It does not prove the phrase appears in that order. It is much better than &#8220;both words are somewhere in this judgment&#8221;, and the phrase still has to be confirmed by reading.</p>



<h2 class="wp-block-heading">Three performance facts</h2>



<p class="wp-block-paragraph"><strong>Row group size is the difference between a skip and a scan.</strong> At 50,000 rows per group, each group in <code>text.parquet</code> held about 80 MB of text, so fetching 200 scattered cases decompressed most of the file and took 29 seconds. At 1,000 rows a group is under a megabyte and the reader skips almost everything.</p>



<p class="wp-block-paragraph"><strong>An IN-list of literals is pushed down; a JOIN is not.</strong> The same 200 lookups written as a join against a temporary table read the entire file: 0.1 seconds became over 40.</p>



<p class="wp-block-paragraph"><strong>A warm cache lied to me twice.</strong> Verifying a phrase by reading the text costs about 20 seconds cold, however few cases it asks for, because a 4 GB store cannot stay resident in 3 GB of RAM. Two earlier measurements of 2.5 seconds were a warm cache, and they misled the design twice before the proximity approach removed the need to read text at query time at all.</p>



<p class="wp-block-paragraph">Where it landed, on 2 threads:</p>



<pre class="wp-block-code"><code>  specific performance   1.02s   8,466 cases
  adverse possession     0.91s
  lis pendens            0.85s
  pattadar pass book     0.92s</code></pre>



<p class="wp-block-paragraph">Against over three minutes before any of this.</p>



<p class="wp-block-paragraph">One further thing had to be designed rather than discovered. Ordering strictly by authority, a window on &#8220;specific performance&#8221; returned 400 Supreme Court cases and nothing at all from 11,429 Telangana candidates. Telangana is the court I appear before. Display slots are now shared between courts, allocated smallest tier first so slack flows onward.</p>



<h2 class="wp-block-heading">What I still do not trust</h2>



<p class="wp-block-paragraph">A corpus is only as useful as its own account of where it is weak.</p>



<p class="wp-block-paragraph"><strong>Two Central Acts cannot be completed from India Code.</strong> The Income-tax Act 1961 and the EPF Act 1952. India Code holds no Act-level PDF for them and only a fraction of the sections. Both are fetched separately.</p>



<p class="wp-block-paragraph"><strong>The Hindu Succession (Andhra Pradesh Amendment) Act 1986</strong> matters for coparcenary devolution before 9 September 2005, and the only copy I have is from PRS Legislative Research, which prints its own disclaimer that the text is unverified. It is deliberately kept off the Act surface and findable only in my own-work index. The real text has to come from the Law Department before it is cited.</p>



<p class="wp-block-paragraph"><strong>Every HMDA document arrived over an unvalidated TLS connection.</strong> All 75 are stamped <code>tls=unverified</code>, because hmda.gov.in&#8217;s certificate chain does not validate. They are usable and they are flagged, and any one of them is checked against the browser copy before it is cited.</p>



<p class="wp-block-paragraph"><strong>OCR misreads digits.</strong> 522 scanned PDFs, 6,738 pages, English and Telugu, banded 222 high confidence, 193 medium, 107 low. A document number came back as <code>NO,P 2418/2013-14</code>. Any survey number, extent or consideration taken from OCR text is checked against the original PDF. Results drawn from OCR print a warning saying so. And the opening pages of a registered deed are the stamp paper, which OCRs as ornament noise, so an OCR&#8217;d deed is judged by its body and not its first lines.</p>



<p class="wp-block-paragraph"><strong>Some sources cannot be harvested at all.</strong> The CCLA site renders a page header and stops: <code>viewActs.do</code>, <code>viewGO.do</code> and <code>viewCirculars.do</code> each return two links and no table. That is not a selector problem, the pages are empty. And <code>goir.telangana.gov.in</code> is an ASP.NET WebForms application with view-state and cascading postbacks, where a result set only exists after a POST carrying the view-state the server has just issued. A listing-page harvester cannot touch it.</p>



<p class="wp-block-paragraph"><strong>A naming rule learned the hard way.</strong> A document&#8217;s title becomes its act name, and lookups match that name loosely. A document titled &#8220;Registration Rules, Telangana — rules under the Registration Act, 1908&#8221; shadowed the Registration Act itself, so asking for section 49 of the Registration Act returned rule 49 instead of section 49, which is the provision on the effect of non-registration. A title must never contain another instrument&#8217;s name.</p>



<p class="wp-block-paragraph"><strong>And one Act answers twice, by design.</strong> HMDA section 5 exists in the corpus in two forms: the Act as originally enacted, naming APIIC, APHB and APSRTC, hand-read by me from printed pages; and the India Code text as adapted for Telangana by G.O.Ms.No.45, Law (F) Department, dated 1 June 2016, naming TSIIC, the Telangana Housing Board and TSTRANSCO. Both are correct. For the composition of the Executive Committee as it stands today, the adapted text governs.</p>



<h2 class="wp-block-heading">What this is actually for</h2>



<p class="wp-block-paragraph">It is not a product and I am not proposing that anyone else run one. It is a tool with a known shape, whose weaknesses are written down where I can see them, which answers in about a second, and which does not stop working if a subscription lapses or a website is redesigned.</p>



<p class="wp-block-paragraph">The thing I did not expect was how much of the work would be auditing rather than building. The scraping was a weekend. The gates, the calibration, the gap analysis, the five defects in the recovery pass and the one-argument bug that mislabelled a quarter of a million judgments as binding — that was the actual project.</p>



<p class="wp-block-paragraph">Which brings it back to HMDA section 5. It was in the corpus the whole time. The lesson is not that the pipeline was bad. It is that a lookup which answers &#8220;no such section&#8221; and a lookup which answers &#8220;here is section 5&#8221; can both be produced by a database that holds the same bytes, and the only thing standing between them is whether anyone checked.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Glossary</h2>



<p class="wp-block-paragraph">This post has two audiences, and each will find half of it obvious. The terms below are defined in the order they appear, on the assumption that no reader knows both halves.</p>



<p class="wp-block-paragraph"><strong>Act surface</strong> — my term for the set of files a section lookup searches, as distinct from the full-text library. A document promoted to the Act surface can be asked for by section number. One left in the library can only be found by searching its words.</p>



<p class="wp-block-paragraph"><strong>Parquet</strong> — a file format for storing tables in columns rather than rows, compressed, with statistics recorded for blocks of rows. A reader can consult those statistics and skip blocks that cannot contain what it wants, without decompressing them.</p>



<p class="wp-block-paragraph"><strong>Row group</strong> — a block of rows inside a parquet file, and the unit that a reader skips or reads. Their size is a design decision, discussed above.</p>



<p class="wp-block-paragraph"><strong>DuckDB</strong> — a database engine that runs inside your program rather than as a server, and reads parquet files directly from disk. No installation to maintain, no daemon running.</p>



<p class="wp-block-paragraph"><strong>Predicate pushdown</strong> — the behaviour that makes the above work: the filter is handed down to the file reader so it can skip data, instead of the reader returning everything and the filter discarding most of it afterwards. An IN-list of literal values is pushed down. A join against another table generally is not.</p>



<p class="wp-block-paragraph"><strong>Full-text index / FTS</strong> — the conventional machinery for searching text at speed, usually a database extension. Not used here, for the sandbox reasons set out above.</p>



<p class="wp-block-paragraph"><strong>Vocabulary and postings</strong> — the two halves of any word index. The vocabulary maps each word to a number. The postings list records, for each word number, where it occurs. Searching is intersecting postings lists.</p>



<p class="wp-block-paragraph"><strong>Posting</strong> — one entry in that list. Here each posting is a triple of word, case and chunk, which is what makes proximity matching possible.</p>



<p class="wp-block-paragraph"><strong>Chunk</strong> — a passage of a judgment, roughly 2,000 characters. Judgments are stored in chunks so that &#8220;these two words appear in the same chunk&#8221; can stand in for &#8220;these two words appear near each other&#8221;.</p>



<p class="wp-block-paragraph"><strong>Proximity matching</strong> — requiring search terms to fall within the same chunk, rather than merely somewhere in the same document. It does not prove the words appear as a phrase in that order.</p>



<p class="wp-block-paragraph"><strong>MINLEN</strong> — the shortest word the index will store. Set to 4 here.</p>



<p class="wp-block-paragraph"><strong>Commonness cut</strong> — the share of documents above which a word is too common to be worth indexing. Set to 10 per cent here, for the reasons measured above.</p>



<p class="wp-block-paragraph"><strong>OCR</strong> — optical character recognition, deriving text from a scanned image. Reliable for prose, unreliable for digits, which is why numbers taken from OCR are checked against the original.</p>



<p class="wp-block-paragraph"><strong>Text layer</strong> — the machine-readable text embedded in a PDF. A PDF produced from a word processor has a good one. A PDF that is a photograph of a page has none, and needs OCR.</p>



<p class="wp-block-paragraph"><strong>Marginal note</strong> — a section heading printed in the margin beside the body text rather than above it. The cause of the HMDA failure: in the text layer the marginal notes interleave with the sentences.</p>



<p class="wp-block-paragraph"><strong>Splitter</strong> — the code that divides a statute into its sections. Most of the failures described here are splitter failures.</p>



<p class="wp-block-paragraph"><strong>Page group</strong> — the fallback when a document cannot be split reliably: it is stored as groups of pages, flagged as unsplit, rather than given section numbers that might be wrong.</p>



<p class="wp-block-paragraph"><strong>Capture ratio</strong> — the proportion of a document&#8217;s text that a split accounts for. Below 92 per cent the split is rejected, so no document can silently lose text.</p>



<p class="wp-block-paragraph"><strong>Digit density</strong> — the proportion of characters in a block that are digits. Prose runs 3 to 4 per cent, schedules 24 per cent and up.</p>



<p class="wp-block-paragraph"><strong>Harvester</strong> — the script that fetches documents from government websites, driven by a manifest describing each source, rather than a separate scraper per site.</p>



<p class="wp-block-paragraph"><strong>Manifest</strong> — the configuration file listing each source, its pages, and how to treat what it returns.</p>



<p class="wp-block-paragraph"><strong>G.O.</strong> — Government Order. A departmental instrument. G.O.Ms. is an Ordinary Order; the number and date identify it.</p>



<p class="wp-block-paragraph"><strong>India Code</strong> — the Government of India&#8217;s official repository of Central and State legislation.</p>



<p class="wp-block-paragraph"><strong>Bifurcation</strong> — the division of Andhra Pradesh on 2 June 2014, creating Telangana. An Andhra Pradesh Act from before that date continues to apply in Telangana; one from after it does not.</p>



<p class="wp-block-paragraph"><strong>Binding and persuasive</strong> — a judgment of a court above you in your own hierarchy binds. A judgment of a court of equal standing elsewhere is persuasive: worth reading, worth citing, but it does not compel a result.</p>



<p class="wp-block-paragraph"><strong>Pattadar pass book</strong> — the Telangana land record document evidencing a cultivator&#8217;s rights. Used above as a search example because it is a phrase of exactly the shape this index handles well.</p>



<p class="wp-block-paragraph"><strong>Coparcenary</strong> — in Hindu law, the body of family members who take an interest in ancestral property by birth. Relevant here because devolution before 9 September 2005 turns on statutory amendments that must be sourced accurately.</p>



<p class="wp-block-paragraph"><strong>Sandbox</strong> — the constrained environment the AI assistant reaches my machine through: 2 CPUs, 3 GB of RAM, restricted network. Several design decisions exist only because of it.</p>



<p class="wp-block-paragraph"><strong>TLS validation</strong> — the check that a website&#8217;s certificate chains to a trusted authority. <code>tls=unverified</code> on a stored document means the download was made without that check succeeding, and the document should be confirmed against the browser copy before it is relied upon.</p>

<p>The post <a href="https://techblog.viveksanghi.com/2026/09/building-a-local-indian-law-corpus/">The section that wasn&#8217;t there</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techblog.viveksanghi.com/2026/09/building-a-local-indian-law-corpus/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">261</post-id>	</item>
		<item>
		<title>A parse error, a stalled upgrade, and fifteen years of silence</title>
		<link>https://techblog.viveksanghi.com/2026/09/parse-error-stalled-upgrade-fifteen-years-of-silence/</link>
					<comments>https://techblog.viveksanghi.com/2026/09/parse-error-stalled-upgrade-fifteen-years-of-silence/#respond</comments>
		
		<dc:creator><![CDATA[Vivek Sanghi]]></dc:creator>
		<pubDate>Thu, 03 Sep 2026 19:29:58 +0000</pubDate>
				<category><![CDATA[blogging]]></category>
		<category><![CDATA[software]]></category>
		<category><![CDATA[sysadmin]]></category>
		<category><![CDATA[wordpress]]></category>
		<category><![CDATA[ai assisted]]></category>
		<category><![CDATA[debugging]]></category>
		<category><![CDATA[dreamhost]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[restore]]></category>
		<category><![CDATA[stubborn fanatic]]></category>
		<category><![CDATA[wp-cli]]></category>
		<guid isPermaLink="false">https://techblog.viveksanghi.com/?p=254</guid>

					<description><![CDATA[<p>My tech blog died in 2011. A stalled WordPress 3.2 upgrade, then a PHP 7 parse error that had been repeating in the logs ever since. What the restore turned up, and the four bugs in the script meant to fix it.</p>
<p>The post <a href="https://techblog.viveksanghi.com/2026/09/parse-error-stalled-upgrade-fifteen-years-of-silence/">A parse error, a stalled upgrade, and fifteen years of silence</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I knew this site was broken. I did not know for how long, or why, and for years I did not look. It sat at techblog.viveksanghi.com returning nothing, and every year that passed made it a slightly larger thing to open.</p>



<p class="wp-block-paragraph">When I finally opened it, the server turned out to have been keeping a precise record of what happened.</p>



<p class="wp-block-paragraph">I did the work over one evening with Claude, which matters to how this reads. I made the decisions and ran every command on my own server; the diagnosis, the scripts and several confident wrong turns came from the model. The wrong turns are in here too, because they were the useful part. Tech is a hobby for me. Law is what earns my living, and a fifteen-year-old broken WordPress had long since stopped being a thing I could justify an evening on. Having something that would grind through log files and SQL dumps without getting bored changed that arithmetic.</p>



<h2 class="wp-block-heading">What the server was saying</h2>



<p class="wp-block-paragraph">The first question was whether anything was alive at all. The domain resolved to my DreamHost IP, same as my other sites, so DNS was fine. HTTPS failed on a certificate error. Over plain HTTP I got a 301 to the www hostname, then a 500 Internal Server Error with an empty response body.</p>



<p class="wp-block-paragraph">An empty 500 is a particular kind of unhelpful. No error on the page, no stack trace, nothing to search for. It means PHP died before producing output, with <code>display_errors</code> off, which on a production host it should be.</p>



<p class="wp-block-paragraph">The useful clue came from the access log rather than the error log:</p>



<pre class="wp-block-code"><code>2026-09-02 21:24:28   200   /favicon.ico</code></pre>



<p class="wp-block-paragraph">A 200. Static files were being served. That one line rules out most of what an empty 500 can mean. Apache was healthy, the virtual host was configured, the document root pointed at the right folder. Only PHP was dying.</p>



<p class="wp-block-paragraph">Which meant anything on the site that was not PHP could still be read. So we asked for the file every WordPress install ships and nobody ever deletes:</p>



<pre class="wp-block-code"><code>curl http://techblog.viveksanghi.com/readme.html</code></pre>



<pre class="wp-block-code"><code>Version 3.1.2
Semantic Personal Publishing Platform</code></pre>



<p class="wp-block-paragraph">WordPress 3.1.2, released April 2011.</p>



<h2 class="wp-block-heading">The one-line cause</h2>



<p class="wp-block-paragraph">The error log had been recording the same entry for years:</p>



<pre class="wp-block-code"><code>[techblog.viveksanghi.com] [warn] mod_fcgid: stderr: PHP Parse error:
syntax error, unexpected token &quot;new&quot; in
/home/viveksanghi/techblog.viveksanghi.com/wp-settings.php on line 219</code></pre>



<p class="wp-block-paragraph">Line 219 of WordPress 3.1.2 reads:</p>



<pre class="wp-block-code"><code>$wp_the_query =&amp; new WP_Query();</code></pre>



<p class="wp-block-paragraph">That <code>=&</code> is assignment by reference to a newly created object. It was correct in PHP 4, where objects were copied on assignment and the reference avoided the copy. PHP 5 made objects handle-based and the construction became pointless. It was deprecated in PHP 5.3 and removed in PHP 7.0.</p>



<p class="wp-block-paragraph">Removed, not deprecated. In PHP 7 and later this is not a warning or a runtime error. It is a parse error. PHP refuses to compile the file, so nothing in it executes, no error handler runs, no output is produced, and Apache has nothing to return but a bare 500.</p>



<p class="wp-block-paragraph">The site did not break from anything I did. It broke when DreamHost moved past PHP 5, and it has been serving 500s to search engines and bots ever since.</p>



<p class="wp-block-paragraph">Could it be patched? The construction appears <strong>252 times</strong> across WordPress core. PHP stops at the first one, but fixing that line only exposes the next. This is not a bug you repair. It is a language incompatibility, and the only answer is a newer WordPress.</p>



<h2 class="wp-block-heading">The timestamp in the root directory</h2>



<p class="wp-block-paragraph">Sitting in the web root was a file called <code>.maintenance</code>, containing one line:</p>



<pre class="wp-block-code"><code>&lt;?php $upgrading = 1309848013; ?&gt;</code></pre>



<p class="wp-block-paragraph">WordPress writes that file when it begins a core update and deletes it when the update finishes. This one was never deleted.</p>



<p class="wp-block-paragraph">That Unix timestamp is <strong>05/07/2011, 06:40 UTC</strong>.</p>



<p class="wp-block-paragraph">WordPress 3.2 was released on 04/07/2011. Alongside <code>.maintenance</code> were two directories in <code>wp-content</code> called <code>wordpress-3.tmp</code> and <code>wordpress-31.tmp</code>, the staging folders the updater uses while unpacking.</p>



<p class="wp-block-paragraph">So: the day after 3.2 came out, I clicked update. It stalled. It left the lock file, the temp directories, and a site still running 3.1.2.</p>



<p class="wp-block-paragraph">The lock file did no harm. WordPress ignores a <code>.maintenance</code> older than ten minutes, a sensible piece of defensive design and the reason the site kept serving afterwards. But it dates the incident to the minute.</p>



<p class="wp-block-paragraph">What the logs cannot tell you is what the site looked like when it stopped.</p>



<p class="wp-block-paragraph">It was running Grid Focus, a theme I had modified by hand over several years until not much of the original remained, with a stack of plugins I had chosen one at a time. The update took it down. I troubleshot for a while. Then life happened, and I lost interest the way you do when a thing stops being fun and starts being a chore.</p>



<p class="wp-block-paragraph">So I let it sit. Somewhere between 2020 and 2022 I paid a freelancer on Fiverr to bring it back, and for a while it worked. That did not last either.</p>



<p class="wp-block-paragraph">Both theme folders were still on the server when I finally went looking. <code>grid_focus_public</code> and <code>gridfocus-v1.5.1b</code>, sitting among fifty other themes I had collected and forgotten, exactly where I left them in 2011.</p>



<h2 class="wp-block-heading">Five years of nobody noticing</h2>



<p class="wp-block-paragraph">The site did not go down in 2011. It went stale in 2011 and died some years later when PHP moved on. The first half is provable.</p>



<p class="wp-block-paragraph">An old <code>wp-dbmanager</code> install had been taking weekly database backups and leaving them in <code>wp-content</code>. There were 134 of them, 2.7 GB in total, running from 29/05/2011 to 31/07/2016.</p>



<p class="wp-block-paragraph">Comparing the oldest against the newest, by table:</p>



<pre class="wp-block-code"><code>techblog_..._20110612.sql   posts: 274,916 bytes   comments: 370,174 bytes
techblog_..._20160731.sql   posts: 274,916 bytes   comments: 370,174 bytes</code></pre>



<p class="wp-block-paragraph">Byte for byte identical. Not a single post or comment changed between June 2011 and July 2016. The blog stopped in 2011 and the backups kept copying the same corpse every Sunday until they stopped too.</p>



<p class="wp-block-paragraph">But the dumps grew from 1.4 MB to 37 MB over those years. Something was expanding. Breaking the 2016 file down by table:</p>



<pre class="wp-block-code"><code>12,549,505  wp_usermeta
   370,174  wp_comments
   342,650  wp_options
   274,916  wp_posts
    79,785  wp_postmeta</code></pre>



<p class="wp-block-paragraph">Twelve and a half megabytes of user metadata against 275 KB of actual writing, reaching about 35 MB by the final backups. Open registration on an unpatched WordPress 3.1.2, and five years of bots signing up. The comments stayed clean, which I put down to Akismet and the captcha plugin, though I cannot prove that from the data. Nothing at all was guarding the registration form.</p>



<p class="wp-block-paragraph">Ninety per cent of my database was junk accounts.</p>



<h2 class="wp-block-heading">Deciding not to repair</h2>



<p class="wp-block-paragraph">Reviving the 3.1.2 install was never really available. There is no supported upgrade path across fifteen years, and the parse error fires before WordPress prints a byte, so I could not reach an admin screen to start one.</p>



<p class="wp-block-paragraph">Rebuilding felt right for a less technical reason. The install carried 44 plugins and 51 themes. Among the plugins were <code>disable-wordpress-core-update</code> and <code>disable-wordpress-plugin-updates</code>, which explains a good deal about why it was still on 3.1.2. Copies of <code>wordpress-28.zip</code> through <code>wordpress-284.zip</code> sat in <code>wp-content</code>, publicly readable. There was a <code>forum</code> directory, empty, and two bbPress integration plugins pointing at it.</p>



<p class="wp-block-paragraph">A WordPress 3.1.2 install had been exposed to the open internet for roughly eight years before PHP killed it, and that version has a long published list of vulnerabilities. There is an irony worth sitting with: the parse error that took the site down has probably been protecting it since about 2019.</p>



<p class="wp-block-paragraph">So: current WordPress, content carried across, nothing else.</p>



<h2 class="wp-block-heading">Getting the content out</h2>



<p class="wp-block-paragraph">The live database was reachable but not trustworthy, and I had 134 dumps provably unchanged since 2011. We took the last one and parsed it directly rather than restoring it anywhere.</p>



<p class="wp-block-paragraph">Two things came out of that which looking at the site would never have shown.</p>



<p class="wp-block-paragraph"><strong>The comments were duplicated.</strong> The table held 840. After normalising whitespace and stripping tags, only 606 were distinct. 234 were exact duplicates of another comment on the same post by the same author, each pair differing only in whether the <code><br /></code> tags had survived:</p>



<pre class="wp-block-code"><code>id=7    2006-09-06 16:54   &#x27;Thanks for viewing my Blog. Well... I must say...&#x27;
id=505  2006-09-06 11:24   &#x27;Thanks for viewing my Blog. Well... I must say...&#x27;</code></pre>



<p class="wp-block-paragraph">The second copies sit in a contiguous block of IDs above 500. The database is called <code>techblog_blogger</code> and the install had a <code>blogger-blogspot-redirect</code> plugin. A Blogger import run twice in 2006, once preserving HTML and once stripping it. Twenty years, and I never noticed, because on the page a duplicate comment just looks like somebody posting twice.</p>



<p class="wp-block-paragraph"><strong>The spam was not where anyone expected.</strong> Given the state of the usermeta table, the assumption was that comments would be full of pharma and casino links. A pattern search for the usual vocabulary returned zero matches. The volume by year decays the way real traffic does, 571 in 2006, 208 in 2007, then single and double digits as the blog went quiet, rather than flooding the way a farmed table does.</p>



<p class="wp-block-paragraph">The first attempt to find spam scored comments by link density. On a blog about Blogger and WordPress hacks that is a terrible signal, and it flagged 43 comments, most of them my own replies containing four or five links to code examples. Claude had produced that heuristic, run it, looked at the output and thrown it away unprompted, which was the right call and not one I would have caught if it had not shown me the list.</p>



<p class="wp-block-paragraph">The second pass went by author name and destination domain and found the real thing: thirteen keyword-anchor link drops, the kind where the author name is the product. &#8220;Mens Suit&#8221; linking to a suit retailer. &#8220;Website Design Atlanta&#8221;, three times. &#8220;beats for sale&#8221;. &#8220;laptop battery&#8221;. Short, on-topic enough, easy to approve by accident in 2010.</p>



<p class="wp-block-paragraph">That left 593 comments, all genuine, threading intact.</p>



<p class="wp-block-paragraph">There was also a post dated 26/12/2020 in a database dumped in 2016. A draft whose <code>post_date</code> carried a mistyped year, its <code>post_modified</code> correctly reading 27/12/2010. It had been sitting ten years in the future for a decade.</p>



<h2 class="wp-block-heading">What the rehearsal caught</h2>



<p class="wp-block-paragraph">Rather than trust the import, we rehearsed it: MariaDB and WP-CLI in a throwaway container, the same WordPress version, a replica of the uploads folder, the real import file.</p>



<p class="wp-block-paragraph">Every expected number came back right except one. <strong>Attachments came out as zero instead of eighteen.</strong></p>



<p class="wp-block-paragraph">The WordPress importer does not simply create attachment records. For each attachment it <em>downloads</em> the file from the URL in <code>wp:attachment_url</code>. In the container that URL was unreachable, so every attachment failed silently.</p>



<p class="wp-block-paragraph">On a real server it is worse than a clean failure. The URL points at the site being restored, over HTTPS, at a moment when the certificate has not been issued. And if the download does succeed, it writes a second copy of every file beside the ones already sitting in <code>wp-content/uploads</code>.</p>



<p class="wp-block-paragraph">The fix is to tell it not to:</p>



<pre class="wp-block-code"><code>wp import export.xml --authors=create --skip=attachment</code></pre>



<p class="wp-block-paragraph">and then register the files already on disk, in place:</p>



<pre class="wp-block-code"><code>find wp-content/uploads -type f \( -iname &#x27;*.jpg&#x27; -o -iname &#x27;*.gif&#x27; \) \
  | grep -vE -- &#x27;-[0-9]+x[0-9]+\.[A-Za-z]+$&#x27; \
  | while read -r f; do wp media import &quot;$f&quot; --skip-copy; done</code></pre>



<p class="wp-block-paragraph">The <code>grep -v</code> matters. Of 56 files in the uploads folder, 36 were WordPress&#8217;s own generated thumbnails. Without that filter every <code>-150x150</code> crop becomes its own media library entry.</p>



<p class="wp-block-paragraph">Nobody would have found this by reading the script. It appeared only by running the whole thing end to end against a real database.</p>



<h2 class="wp-block-heading">Four ways the deploy script was wrong</h2>



<p class="wp-block-paragraph">This is the part I would not have predicted before the evening started. The script that rebuilt the site was written for me to paste into an SSH session, and it had to be fixed four times before it ran clean. Every failure was caught by the script&#8217;s own preflight, before anything on my server was touched.</p>



<p class="wp-block-paragraph"><strong><code>getent</code> does not exist on DreamHost&#8217;s shell.</strong> The DNS check used it. The script would have aborted in preflight with a confusing error. Replaced with a PHP lookup, since PHP is guaranteed present there.</p>



<p class="wp-block-paragraph"><strong><code>pipefail</code> and <code>head</code> do not mix.</strong> The script ran <code>wp --info | head -3</code>. <code>head</code> closes the pipe after three lines, <code>wp</code> gets SIGPIPE and exits 141, and <code>set -o pipefail</code> with <code>set -e</code> turns that into a silent abort. The three lines of output on my screen were the last thing the script printed before killing itself, and the failure looked like nothing at all.</p>



<p class="wp-block-paragraph"><strong>A <code>$</code> in a password.</strong> My admin password contained <code>$4</code>. Inside double quotes bash expands that as a positional parameter, which does not exist, and <code>set -u</code> correctly aborted. The config block now uses single quotes.</p>



<p class="wp-block-paragraph"><strong><code>--raw</code> wrote an undefined constant.</strong> This one actually reached my server. The script ran <code>wp config set WP_AUTO_UPDATE_CORE minor --raw</code>, which writes <code>define('WP_AUTO_UPDATE_CORE', minor);</code> without quotes. In PHP 8 an undefined constant is a fatal error, so WordPress could not load at all, and the script reported it as &#8220;cannot reach the database&#8221; — which was wrong and sent me looking in the wrong place.</p>



<p class="wp-block-paragraph">There was a fifth, structural, caught before I ran anything: the original script tested the database connection <em>after</em> renaming my site directory. If the database had been unreachable, it would have moved my entire site aside and then died. That ordering was reversed so every destructive step sits behind a check that has already passed.</p>



<p class="wp-block-paragraph">I record these because the honest version of &#8220;an AI helped me fix my site&#8221; includes the four times it handed me something broken. What made it work was not that the output was right first time. It was that each failure was cheap, legible, and caught before it cost anything.</p>



<h2 class="wp-block-heading">The redirect loop I caused myself</h2>



<p class="wp-block-paragraph">With WordPress installed and content imported, the site returned a redirect loop. <code>techblog.viveksanghi.com</code> redirected to <code>www.techblog.viveksanghi.com</code>, which redirected straight back, forever.</p>



<p class="wp-block-paragraph">We assumed the host was misconfigured. I opened a support ticket saying so. One command showed we were half wrong:</p>



<pre class="wp-block-code"><code>curl -sI http://techblog.viveksanghi.com/readme.html      → 301
curl -sI http://www.techblog.viveksanghi.com/readme.html  → 200 OK</code></pre>



<p class="wp-block-paragraph">A static file that exists on disk gets redirected at the bare hostname and served at www. That first redirect happens above <code>.htaccess</code>, in the host&#8217;s virtual host configuration, and nothing on my side could override it. That half was DreamHost&#8217;s, and they removed it inside a couple of hours when asked properly.</p>



<p class="wp-block-paragraph">The second redirect was mine. Static files at www served fine and only WordPress-handled paths bounced. That is <code>redirect_canonical</code> doing its job, because <code>siteurl</code> and <code>home</code> were set to the bare hostname while the server was serving www. Each half behaved correctly. Together they were an infinite loop.</p>



<p class="wp-block-paragraph">Testing a static file rather than the homepage is what separated them. On the homepage both redirects are in play and the trace tells you nothing about which layer owns which. I cancelled the first ticket and filed a more accurate one.</p>



<h2 class="wp-block-heading">What I would tell someone with a site in this state</h2>



<p class="wp-block-paragraph"><strong>Test a static file first.</strong> Whether <code>readme.html</code> serves tells you in one request whether the web server is broken or only PHP is. Almost everything that followed came out of that one 200.</p>



<p class="wp-block-paragraph"><strong>The error log is the whole answer, and it is not in the panel.</strong> The hosting control panel showed access logs and traffic graphs for days. The actual parse error, repeating identically for years, was in a file on disk.</p>



<p class="wp-block-paragraph"><strong>Check whether your content changed before you value your backups.</strong> 134 weekly backups sound like excellent hygiene. Comparing the first against the last showed 134 copies of the same 2011 content, with only the spam growing. Backup count measures nothing.</p>



<p class="wp-block-paragraph"><strong>Rehearse the restore.</strong> A throwaway database and a full dry run took under an hour and caught a defect that would have cost the entire media library, silently, with a success message.</p>



<p class="wp-block-paragraph"><strong>Suspect your own configuration before your host&#8217;s.</strong> I was three quarters of the way to blaming DreamHost for a redirect loop I had created myself.</p>



<p class="wp-block-paragraph"><strong>If you are working with a model, make it show you its output, not just its conclusions.</strong> Every one of the four script defects and the bad spam heuristic surfaced because something got printed and looked wrong. None of them surfaced from reading the code.</p>



<p class="wp-block-paragraph">The site is back. Seventy-five posts, 17/04/2006 to 25/05/2011, and 593 comments, on WordPress 7.1 with a clean database and no plugins carried across.</p>



<p class="wp-block-paragraph">Sixty-eight images in the older posts still point at <code>photos1.blogger.com</code> and the <code>bp1</code> to <code>bp3</code> hosts, which Google retired years ago. Those have been broken far longer than the site was, and there is nothing left to restore them from. I am inclined to leave them, with a note. A blog that ran from 2006 should be allowed to show its age somewhere.</p>

<p>The post <a href="https://techblog.viveksanghi.com/2026/09/parse-error-stalled-upgrade-fifteen-years-of-silence/">A parse error, a stalled upgrade, and fifteen years of silence</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techblog.viveksanghi.com/2026/09/parse-error-stalled-upgrade-fifteen-years-of-silence/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">254</post-id>	</item>
		<item>
		<title>ipad 2 – My App List and usage in India</title>
		<link>https://techblog.viveksanghi.com/2011/05/ipad-2-my-app-list-and-usage-in-india/</link>
		
		<dc:creator><![CDATA[Vivek Sanghi]]></dc:creator>
		<pubDate>Wed, 25 May 2011 09:46:35 +0000</pubDate>
				<category><![CDATA[apple]]></category>
		<category><![CDATA[ipad]]></category>
		<guid isPermaLink="false">https://techblog.viveksanghi.com/?p=219</guid>

					<description><![CDATA[<p>Hi, seems like an eternity has passed since my last post. However I was really away from the tech world building a career for my self. While I am still engaged in a business of my own, I did want to write today. It&#8217;s been over a month and a half since I have been [&#8230;]</p>
<p>The post <a href="https://techblog.viveksanghi.com/2011/05/ipad-2-my-app-list-and-usage-in-india/">ipad 2 &#8211; My App List and usage in India</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Hi, seems like an eternity has passed since my last post. However I was really away from the tech world building a career for my self. While I am still engaged in a business of my own, I did want to write today.</p>
<p>It&#8217;s been over a month and a half since I have been using ipad 2 (3g+wifi 64 GB) here in India. Thankfully I have a good wifi setup at home where I could use and update ipad after my work hours. ipad is an impressive device and very easy to use as well. Once you learn how to get your way around ipad&#8217;s app store, itunes etc you might actually end up spending more time on your ipad and less on your notebook.</p>
<p>I have found ipad to be a great device for almost all basic work such as browsing, sending emails. listening to music, watching movies, videos etc However it isn&#8217;t a great option for reading books, magazines etc. Long periods of reading on ipad are tiring for a couple of reasons:</p>
<p>1. Screen brightness causes eye fatigue<br />
2. ipad is kind of heavy of holding in hand (esp while travelling and in bed) and reading for a long time<br />
3. Lack of a single store or app for meeting all my book/magazine/newspaper reading requirements</p>
<p>However I&#8217;d still give ipad a very high score for all the other things that it can do. Below is a list of various apps I use regularly</p>
<p><strong>Reading</strong></p>
<p>Flipad<br />
Zinio<br />
Kindle<br />
iBooks</p>
<p>&nbsp;</p>
<p><strong>News</strong></p>
<p>CNN<br />
NDTV<br />
TOI (Times of India app)</p>
<p>&nbsp;</p>
<p><strong>Games</strong></p>
<p>Trade Nations<br />
Contract Killer<br />
Can Knockdown<br />
Overkill<br />
tictac free</p>
<p><strong>Video Player</strong></p>
<p>Azul Media Player</p>
<p>&nbsp;</p>
<p><strong>File Transfer</strong></p>
<p>USB Disk<br />
Dropbox</p>
<p>&nbsp;</p>
<p><strong>Fitness</strong></p>
<p>Dailyburn</p>
<p>&nbsp;</p>
<p><strong>Other Apps</strong></p>
<p>Compass<br />
Truphone<br />
xmarks (bookmark synchronization)</p>
<p>Some of these apps esp the games contain in-app purchases and can be pretty addictive.</p>
<p><strong>3G Usage</strong></p>
<p>I have used BSNL 3G on my ipad and found it to be pretty impressive and fast. I had to cut up my regular SIM into a micro SIM and use it on ipad. However, BSNL now offers ipad plans (prepaid and postpaid). Unfortunately pre-paid SIMs for iPad are currently unavailable and post paid as only one plan i.e. Rs 999/- unlimited</p>
<p>Other players such as airtel, Tata Docomo etc do not seem to have any inexpensive data plans for 3g. I hope they come out with some good competitive plans for 3G data users.</p>
<p>The post <a href="https://techblog.viveksanghi.com/2011/05/ipad-2-my-app-list-and-usage-in-india/">ipad 2 &#8211; My App List and usage in India</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">219</post-id>	</item>
		<item>
		<title>Disqus Comment System for WordPress</title>
		<link>https://techblog.viveksanghi.com/2010/03/disqus-comment-system-for-wordpress/</link>
					<comments>https://techblog.viveksanghi.com/2010/03/disqus-comment-system-for-wordpress/#comments</comments>
		
		<dc:creator><![CDATA[Vivek Sanghi]]></dc:creator>
		<pubDate>Wed, 17 Mar 2010 12:43:52 +0000</pubDate>
				<category><![CDATA[social]]></category>
		<category><![CDATA[software]]></category>
		<category><![CDATA[wordpress]]></category>
		<category><![CDATA[comment]]></category>
		<guid isPermaLink="false">https://techblog.viveksanghi.com/?p=214</guid>

					<description><![CDATA[<p>This blog is now using an interesting new comment system called DISQUS. I like Disqus for the following reasons: 1. It allows users to log in via multiple sites such as Twitter, Facebook, Open Id, Yahoo and Disqus itself 2. It allows users to track all their comments made across multiple sites, at a single [&#8230;]</p>
<p>The post <a href="https://techblog.viveksanghi.com/2010/03/disqus-comment-system-for-wordpress/">Disqus Comment System for WordPress</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>This blog is now using an interesting new comment system called <a title="DISQUS Comments" href="http://disqus.com/" target="_blank">DISQUS</a>. I like Disqus for the following reasons:</p>
<p>1. It allows users to log in via multiple sites such as Twitter, Facebook, Open Id, Yahoo and Disqus itself</p>
<p>2. It allows users to track all their comments made across multiple sites, at a single place</p>
<p>3. When users manage their comments through Disqus and / or log into Disqus via one of their social networking accounts, the comment they make also gets published in their social networking feed resulting in greater propagation of the source post where the comment was made</p>
<p>4. Incredibly easy and smooth <a href="http://disqus.com/comments/wordpress/" target="_blank">integration</a> with wordpress</p>
<p>The post <a href="https://techblog.viveksanghi.com/2010/03/disqus-comment-system-for-wordpress/">Disqus Comment System for WordPress</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techblog.viveksanghi.com/2010/03/disqus-comment-system-for-wordpress/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">214</post-id>	</item>
		<item>
		<title>bbPress: Installation and Deep Integration with WordPress</title>
		<link>https://techblog.viveksanghi.com/2009/12/bbpress-installation-and-deep-integration-with-wordpress/</link>
					<comments>https://techblog.viveksanghi.com/2009/12/bbpress-installation-and-deep-integration-with-wordpress/#comments</comments>
		
		<dc:creator><![CDATA[Vivek Sanghi]]></dc:creator>
		<pubDate>Sat, 19 Dec 2009 17:00:07 +0000</pubDate>
				<category><![CDATA[bbpress]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[software]]></category>
		<category><![CDATA[wordpress]]></category>
		<category><![CDATA[forum]]></category>
		<guid isPermaLink="false">https://techblog.viveksanghi.com/?p=186</guid>

					<description><![CDATA[<p>bbPress is a very easy to use forum or bulletin board software from the creators of wordpress. This article will cover installation of bbpress for a wordpress blog and its deep integration with wordpress blog such that the blog and the forum share common logins, cookies and user databases.</p>
<p>The post <a href="https://techblog.viveksanghi.com/2009/12/bbpress-installation-and-deep-integration-with-wordpress/">bbPress: Installation and Deep Integration with WordPress</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Hello friends. It&#8217;s been a long time since I have posted any new articles here. This is because I have been keeping very busy with my work life and find no time to research and explore blogger/wordpress code.</p>
<p>Recently I learnt about a forum software by name bbpress which can integrate well with wordpress blogs. I decided to squeeze time out of my schedule to try this software and write about it.</p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/bbwp.jpg"><img decoding="async" class="aligncenter size-medium wp-image-203" title="bbPress is a forum software launched by makers of wordpress" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/bbwp-300x54.jpg" alt="" width="300" height="54" /></a></p>
<p>bbPress is a very easy to use forum or bulletin board software from the creators of wordpress. To quote their website:</p>
<p>&#8220;bbPress is focused on web standards, ease of use, ease of integration, and speed. We’re keeping things as small and light as possible while still allowing for great add-on features through our extensive plugin system.&#8221;</p>
<p>This article will cover installation of bbpress for a wordpress blog and its deep integration with wordpress blog such that the blog and the forum share common logins, cookies and user databases. Make a note of the versions used before you proceed to read the installation/integration tutorial:</p>
<p>bbPress version: 1.0.2<br />
Wordpress version: 2.8.9</p>
<p><strong>bbPress download and server setup</strong></p>
<p>&#8211; As a first step you need to download bbpress from <a title="bbpress download" href="http://bbpress.org/download/" target="_blank">here</a>. Unzip and rename the folder to what ever you like. Whatever name you choose will also become a part of your forum URL in the following format:</p>
<p>&lt;wordpress blog URL&gt;/&lt;bbPress folder name&gt;</p>
<p>&#8211; Now upload this folder under you main wordpress directory on the server where you are hosting your blog. For example, I had renamed my bbPress directory to &#8220;forum&#8221; and uploaded it under the folder techblog.viveksanghi.com on the webserver such that my forum url become:</p>
<p>https://techblog.viveksanghi.com/forum</p>
<p><strong>WordPress Setup</strong></p>
<p><span style="text-decoration: underline;">bbPress Integration Plugin</span></p>
<p>&#8211; Now login into you wordpress admin panel, install the plugin <a title="bbPress Integration plugin for wordpress" href="http://wordpress.org/extend/plugins/bbpress-integration/" target="_blank">bbpress integration</a> into wordpress and activate the plugin. On the plugin’s configuration page enter the URL of your bbpress forum which will be in the following format as already explained earlier:</p>
<p>&lt;yourblog URL&gt;/&lt;bbpress folder name&gt;.</p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/bbintegration-wpplugin.jpg" target="_blank"><img decoding="async" class="aligncenter size-medium wp-image-188" title="click to view an enlarged image" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/bbintegration-wpplugin-300x108.jpg" alt="" width="300" height="108" /></a></p>
<p>Do not change anything else and hit the save button. Scroll down on the same page and you will be shown some code that you need to enter into your wordpress config file. Make a note of this code as we will require it in the next step.</p>
<p><span style="text-decoration: underline;">WordPress Config</span></p>
<p>&#8211; Apart from the above-mentioned piece of code from the previous step, there are some additional changes that you need to make to your wordpress config file. Let’s looks are what additions are to be made to the wordpress configuration file. Download and edit your wordpress config file. This will be located under your main wordpress folder by the name <em>wp-config.php</em></p>
<p>You have to enter the following values in the wp-config.php file:</p>
<p>AUTH_KEY<br />
SECURE_AUTH_KEY<br />
LOGGED_IN_KEY</p>
<p>Go <a title="Generate random Secret keys" href="http://api.wordpress.org/secret-key/1.1/" target="_blank">here</a> to generate complex keys for the above mentioned items. Then paste these keys in the wp-config.php file as shown below:</p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/wp-config-01.jpg"><img fetchpriority="high" decoding="async" class="aligncenter size-medium wp-image-195" title="wp-config-01" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/wp-config-01-300x209.jpg" alt="" width="300" height="209" /></a></p>
<p>Also paste the code we generated earlier from the bbpress integration plugin of wordpress as shown in the image above.</p>
<p>Now its time to start your bbPress installation. However make sure to have the following details about your wordpress database handy as we will use the same database for wordpress and bbpress.</p>
<p>&#8211; WordPress database name<br />
&#8211; WordPress database hostname (Note that it will not be “localhost” if you are hosting on dreamhost or other such hosting site)<br />
&#8211; WordPress database username<br />
&#8211; WordPress database password<br />
&#8211; WordPress table prefix</p>
<p>In case you are new to using databases and/or are not sure where to find the above mentioned details, then you might want to install the <a title="WP DB Manager" href="http://wordpress.org/extend/plugins/wp-dbmanager/" target="_blank">WP-DBManager</a> plugin in wordpress and should be able to view all these above details from within wordpress.</p>
<p>Once these details are ready, you may start the installation by proceeding to the following link:</p>
<p>[your bbPress forum URL]/bb-admin/options-wordpress.php</p>
<p><strong>Step 1 – Database configuration</strong></p>
<p>First you will be presented with the following screen:</p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/bbpress-step01-01.jpg"><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-190" title="Click on the image to view an enlarged image" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/bbpress-step01-01-300x210.jpg" alt="" width="300" height="210" /></a></p>
<p>Now enter the database details you collated earlier, as shown in the image below. You may also use the question mark symbols to learn more about each of these fields.</p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/bbpress-step01-02.jpg"><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-191" title="Click on the image to view an enlarged image" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/bbpress-step01-02-300x239.jpg" alt="" width="300" height="239" /></a></p>
<p>And then proceed to step 2.</p>
<p><strong>Step 2 – WordPress Integration Settings</strong></p>
<p>To fill in the details required for step 2, load the options screen of your wordpress blog using the following link:</p>
<p><a href="http://www.diary.viveksanghi.com/wp-admin/options.php"></a></p>
<p>[wordpress link]/wp-admin/options.php</p>
<p>On the bbpress installation page check integration and cookie integration settings as shown in the image. Now add the auth cookie, secure auth cookie and logged in key values (we created these values earlier before starting installation of bbpress) from the <em>wp-config.php</em> file. The salt values will be available from the wordpress options screen we loaded a while ago.</p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/bbpress-Step02-01.jpg"><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-192" title="Click on the image to view an enlarged image" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/bbpress-Step02-01-300x260.jpg" alt="" width="300" height="260" /></a></p>
<p>If you do not find the secure auth cookie salt value, you may leave it blank and it should not be a problem. Then scroll down and also check user database integration settings. Fill in the values from the database values that you noted earlier. Most of you will only require the wordpress database table prefix. Now save the integration settings and you will be prompted to proceed to step three.</p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/bbpress-Step02-02.jpg"><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-193" title="Click on the image to view an enlarged image" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/bbpress-Step02-02-300x129.jpg" alt="" width="300" height="129" /></a></p>
<p><strong>Step 3 &#8211; Site settings</strong></p>
<p>Fill in a name for your forum and the site URL as shown below. Site URL will depend on the folder name you gave to the bbpress folder before uploading it under wordpress folder.</p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/bbpress-Step03-01.jpg"><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-194" title="Click on the image to view an enlarged version" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/bbpress-Step03-01-300x205.jpg" alt="" width="300" height="205" /></a></p>
<p>Select the wordpress admin name from the drop down as the bbpress keymaster unless you wish to alot this supreme role to anyone else. Give a name for your first forum, then hit the save site settings button and you should be ready to proceed to your forum page.</p>
<p>Now go to the admin panel of your forum and select wordpress integration under the settings tab as shown in the image below.  Here you have to map user roles between wordpress and bbPress users as the user list is now synchronized and/or common between the two.</p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/bbpress-integration.jpg"><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-189" title="Click on the image to view an enlarged image" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/12/bbpress-integration-300x124.jpg" alt="" width="300" height="124" /></a></p>
<p>Once you save these settings, your wordpress and bbPress will be integrated and you are ready to go.</p>
<p>I am also planning to research into a system/plugin such that the wordpress posts become topics and the comments on each wordpress post become forum posts under these topics. If I find out some thing on these lines, I will definitely update this post or create a second post on this topic.</p>
<p><strong>References</strong>: I researched the bbPress forums extensively and did a lot of hands on tinkering to learn how to install bbPress and integrate it with wordpress. This <a title="bbPress Forums" href="http://bbpress.org/forums/topic/wordpress-and-bbpress-integration-101" target="_blank">link</a> was particularly helpful in my learning process.</p>
<p>The post <a href="https://techblog.viveksanghi.com/2009/12/bbpress-installation-and-deep-integration-with-wordpress/">bbPress: Installation and Deep Integration with WordPress</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techblog.viveksanghi.com/2009/12/bbpress-installation-and-deep-integration-with-wordpress/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">186</post-id>	</item>
		<item>
		<title>Blogging on custom domains using Blogger and WordPress with and without self hosting</title>
		<link>https://techblog.viveksanghi.com/2009/01/blogging-on-custom-domains-using-blogger-and-wordpress-with-and-without-self-hosting/</link>
					<comments>https://techblog.viveksanghi.com/2009/01/blogging-on-custom-domains-using-blogger-and-wordpress-with-and-without-self-hosting/#comments</comments>
		
		<dc:creator><![CDATA[Vivek Sanghi]]></dc:creator>
		<pubDate>Sun, 11 Jan 2009 20:32:55 +0000</pubDate>
				<category><![CDATA[blogger]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[wordpress]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://techblog.viveksanghi.com/?p=167</guid>

					<description><![CDATA[<p>&#8216;lo, In this article I will explain how you can: 1. Blog on a custom domain (eg. www.&#60;yourname&#62;.com) while not having to buy any hosting space on the web (Will cover both Blogger and WordPress) 2. Blog on a custom domain while also having custom hosting (Will cover only wordpress and explain how to migrate blogger blogs [&#8230;]</p>
<p>The post <a href="https://techblog.viveksanghi.com/2009/01/blogging-on-custom-domains-using-blogger-and-wordpress-with-and-without-self-hosting/">Blogging on custom domains using Blogger and WordPress with and without self hosting</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>&#8216;lo,</p>
<p>In this article I will explain how you can:</p>
<p>1. Blog on a custom domain (eg. www.&lt;yourname&gt;.com) while not having to buy any hosting space on the web (Will cover both Blogger and WordPress)</p>
<p>2. Blog on a custom domain while also having custom hosting (Will cover only wordpress and explain how to migrate blogger blogs to custom/self-hosted wordpress)</p>
<p>I understand that many readers may question the need for this article when there are plenty or articles on the web. However I will try to make it very simple and easy to follow with judicious use of screenshots to make life simple.</p>
<p>Before proceeding, let&#8217;s note that one can always start of a free blog on popular blogging platforms like Google&#8217;s Blogger or WordPress. The URL of a blog on these free services will be like:</p>
<p>&lt;example&gt;.blogspot.com</p>
<p>&lt;example&gt;.wordpress.com</p>
<p>If you are new to blogging, I recommend that you first try out hosting via these free platforms and see if you are able to maintain a good posting frequency. Once seasoned, go ahead and buy web hosting etc and host the blog by yourself.</p>
<p><strong>Blog on a custom domain (eg. www.&lt;yourname&gt;.com) while not having to buy any hosting space on the web (Will cover both Blogger and WordPress)</strong></p>
<p>If you have a blog on Blogger or WordPress.com, you might be pleased to know that both these platforms allow you to buy a custom domain name and make that the URL address of your blog.</p>
<p><strong>Blogger</strong></p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/01/blogger1.jpg" target="_blank"><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-168" title="Click to expand image (opens in a new tab/window)" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/01/blogger1-300x175.jpg" alt="blogger1" width="300" height="175" align="center" /></a></p>
<p>1. To view the custom domain settings facility in your blogger account, just go to the settings -&gt; publishing area as shown in the image <strong>above</strong> (expand image to read the instructions or just follow the on screen instructions in your account. It&#8217;s very easy!)</p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/01/blogger2.jpg" target="_blank"><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-169" title="Click to expand image (opens in a new tab/window)" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/01/blogger2-300x190.jpg" alt="Click to expand image (opens in a new tab/window)" width="300" height="190" /></a></p>
<p>2. If you choose to buy a domain from Google, use this page as shown in the image <strong>above</strong> (read the price and the time period for which you are paying very carefully). As of now Google is using godaddy.com as partner for the domain name registrations and is also providing Google Apps on the domain. Which probably means you can have an email address like &lt;yourname&gt;@&lt;yourdomain&gt;.com .</p>
<p>3. However, if you have already bought a domain elsewhere, then click on the &#8220;Switch to advanced settings&#8221; link. You will be present with a page which looks like this:</p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/01/blogger3.jpg" target="_blank"><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-170" title="Click to expand image (Opens in a new tab/window)" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/01/blogger3-300x103.jpg" alt="Click to expand image (Opens in a new tab/window)" width="300" height="103" /></a></p>
<p>4. Click on the link &#8220;setup instructions&#8221; and read it very carefully before you proceed with this step. You will have to create a CNAME (<a href="http://www.webopedia.com/TERM/C/CNAME.html" target="_blank">Canonical Name</a>) record. This will make your domain point to Google&#8217;s servers such that when someone visits your domain, the request is forwarded to Google servers and they can fetch the appropriate blog (i.e. your blogspot blog) and display it to the visitor.</p>
<p>Blogger help provides <a href="http://help.blogger.com/bin/answer.py?answer=58317" target="_blank">specific instructions</a> on setting up CNAME for certain domain providers. If you do not plan to buy your domain from Google and then I recommend buying it from one of the service providers listed on that page.</p>
<p>This step can be troublesome and you may feel free to <a href="http://www.viveksanghi.com/contact" target="_blank">contact me</a> if you need any assistance. However note that I may be slow in responding and the real fun is in trying it out on your own <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>5. Once everything is done, enter the word verification and save your settings. Give it some time and then your blog will start working on your custom domain URL. Your old blogspot address will automatically redirect to your new custom domain.</p>
<p>As always, if you face problems, you may redirect your questions to me and I will direct you in the right direction <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f61b.png" alt="😛" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p><strong>WordPress</strong></p>
<p>If you have not already purchased a domain, then just like Blogger, wordpress also allows you to purchase a domain + redirect your wordpress.com blog to it for USD 15.00 (on this date).</p>
<p>You can also have your custom email by using Google apps on this newly purchased domain (<a href="http://support.wordpress.com/domain-mapping/custom-email/" target="_blank">learn more</a>)</p>
<p><strong>Buy a domain from wordpress and use it for your wordpress.com blog</strong></p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/01/wp2.jpg" target="_blank"><img loading="lazy" decoding="async" class="alignright size-full wp-image-174" title="Upgrades -&gt; Domains" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/01/wp2.jpg" alt="Upgrades -&gt; Domains" width="165" height="243" /></a></p>
<p>1. Go to the &#8220;domains&#8221; section under &#8220;upgrades&#8221; on your wordpress.com dashboard (See the image).</p>
<p>2. On the page that appears, enter the domain you wish to use for your blog and click on &#8220;Add Domain to Blog&#8221; button (See the image below). A page will appear asking you to buy 15 credits to complete this transaction (unless you already have credits in your account). Note that you must have a Paypal account to purchase credits (currently 1 credit = USD 1.00).</p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/01/wp1-2.jpg" target="_blank"><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-172" title="Click to expand image (opens in a new tab/window)" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/01/wp1-2-300x121.jpg" alt="wordpress2" width="300" height="121" /></a></p>
<p>3. Then you have to make sure (refer to the image below) that your newly purchased domain is recognized as your blog URL on the &#8220;domains&#8221; page and your wordpress.com blog redirects to your custom domain (also called <a href="http://support.wordpress.com/domain-mapping/" target="_blank">domain mapping</a>).</p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/01/wp1-3.jpg" target="_blank"><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-173" title="Click to expand image (Opens in a new tab/window)" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/01/wp1-3-300x47.jpg" alt="Click to expand image (Opens in a new tab/window)" width="300" height="47" /></a></p>
<p>That&#8217;s it. You are ready.</p>
<p>You might want to <a href="http://support.wordpress.com/domain-mapping/domain-management/" target="_blank">learn more</a> about managing domains purchased through wordpress.</p>
<p><strong>Using your separately purchased domain for your wordpress.com blog</strong></p>
<p>1. <a href="http://support.wordpress.com/domain-mapping/" target="_blank">Visit this link</a> and setup your domain to be used in wordpress domain mapping process. All you have to do is add the nameservers shown there to your domain. Remove any existing nameservers from your domain.</p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/01/wp1-2.jpg" target="_blank"><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-172" title="Click to expand image (Opens in a new window/tab)" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/01/wp1-2-300x121.jpg" alt="Click to expand image. Opens in a new window/tab." width="300" height="121" /></a></p>
<p>2. Once your domain is ready go to the &#8220;domains&#8221; section under upgrades, on your wordpress.com dashboard. Enter the domain name you wish to use as shown in the image above and click on the &#8220;Add Domain to Blog&#8221; button. If your nameservers are set correctly and have propagated, then WordPress will allow you to buy 10 credits to enable this domain mapping.</p>
<p>3. Now go to step 3 in the previous section (above) and you will be ready</p>
<p>If you ever have any problems regarding domain mapping, please do visit <a href="http://en.forums.wordpress.com/tags/domain-mapping" target="_blank">this link</a> to seek help.</p>
<p><strong>Alternate way to buy WordPress credits</strong></p>
<p>Log into your wordpress.com account go to the gifts section under upgrades as shown in the image below:</p>
<p><a href="https://techblog.viveksanghi.com/wp-content/uploads/2009/01/wp3.jpg" target="_blank"><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-175" title="Click to expand image (Opens in a new window/tab)" src="https://techblog.viveksanghi.com/wp-content/uploads/2009/01/wp3-300x221.jpg" alt="wordpress" width="300" height="221" /></a></p>
<p>While this page will allow you to gift the credits to other wordpress users, you may simply use it to purchase credits for your wordpress.com account.</p>
<p>I hope this article will make life simpler for everybody who is new to blogging platforms. Stay tuned for my next article where I will talk about having a blog on a custom domain with custom hosting.</p>
<p>The post <a href="https://techblog.viveksanghi.com/2009/01/blogging-on-custom-domains-using-blogger-and-wordpress-with-and-without-self-hosting/">Blogging on custom domains using Blogger and WordPress with and without self hosting</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techblog.viveksanghi.com/2009/01/blogging-on-custom-domains-using-blogger-and-wordpress-with-and-without-self-hosting/feed/</wfw:commentRss>
			<slash:comments>9</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">167</post-id>	</item>
		<item>
		<title>Welcome to the new location of Stubborn Fanatic</title>
		<link>https://techblog.viveksanghi.com/2008/12/welcome-to-the-new-location-of-stubborn-fanatic/</link>
					<comments>https://techblog.viveksanghi.com/2008/12/welcome-to-the-new-location-of-stubborn-fanatic/#comments</comments>
		
		<dc:creator><![CDATA[Vivek Sanghi]]></dc:creator>
		<pubDate>Sun, 28 Dec 2008 20:15:45 +0000</pubDate>
				<category><![CDATA[blogging]]></category>
		<category><![CDATA[blogspot]]></category>
		<category><![CDATA[stubborn fanatic]]></category>
		<guid isPermaLink="false">https://techblog.viveksanghi.com/?p=164</guid>

					<description><![CDATA[<p>Hello All, I have successfully redirected stubborn-fanatic.blogspot.com to techblog.viveksanghi.com. The new home for this blog. Those of you who were looking at my blogspot template for design ideas will be unable to do so any more. However I can send you my blogspot template&#8217;s full code for a nominal payment of $20 via paypal only. [&#8230;]</p>
<p>The post <a href="https://techblog.viveksanghi.com/2008/12/welcome-to-the-new-location-of-stubborn-fanatic/">Welcome to the new location of Stubborn Fanatic</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Hello All,</p>
<p>I have successfully redirected stubborn-fanatic.blogspot.com to techblog.viveksanghi.com. The new home for this blog.</p>
<p>Those of you who were looking at my blogspot template for design ideas will be unable to do so any more. However I can send you my blogspot template&#8217;s full code for a nominal payment of $20 via paypal only. If you are interested, please contact me using this <a title="Contact Me" href="http://www.viveksanghi.com/contact/" target="_blank">link</a> for further instructions.</p>
<p>The post <a href="https://techblog.viveksanghi.com/2008/12/welcome-to-the-new-location-of-stubborn-fanatic/">Welcome to the new location of Stubborn Fanatic</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techblog.viveksanghi.com/2008/12/welcome-to-the-new-location-of-stubborn-fanatic/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">164</post-id>	</item>
		<item>
		<title>Contest Suggestion: 31 Days of the Dragon</title>
		<link>https://techblog.viveksanghi.com/2008/05/contest-suggestion-31-days-of-the-dragon/</link>
		
		<dc:creator><![CDATA[Vivek Sanghi]]></dc:creator>
		<pubDate>Wed, 14 May 2008 19:31:00 +0000</pubDate>
				<category><![CDATA[internet]]></category>
		<category><![CDATA[software]]></category>
		<category><![CDATA[suggested]]></category>
		<guid isPermaLink="false">https://techblog.viveksanghi.com/2008/05/contest-suggestion-31-days-of-the-dragon/</guid>

					<description><![CDATA[<p>&#8216;lo. After a long time I am posting some thing here for you guys. I hope until now you have enjoyed the existing set of Blogger mods that I had published. While this is not related to blogger mods, I am sure those of you who were unaware, will definitely love it. The other day [&#8230;]</p>
<p>The post <a href="https://techblog.viveksanghi.com/2008/05/contest-suggestion-31-days-of-the-dragon/">Contest Suggestion: 31 Days of the Dragon</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>&#8216;lo. After a long time I am posting some thing here for you guys. I hope until now you have enjoyed the existing set of Blogger mods that I had published. While this is not related to blogger mods, I am sure those of you who were unaware, will definitely love it. The other day I came across a big contest while browsing some of sites I read regularly, and simply could not resist sharing it with you.<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_XGtsagQTuUQ/SCs-XA8IRhI/AAAAAAAAAHY/eANxtSrJcY8/s1600-h/hp-pavilion-hdx-entertainment-notebook.jpg"><img decoding="async" style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://bp1.blogger.com/_XGtsagQTuUQ/SCs-XA8IRhI/AAAAAAAAAHY/eANxtSrJcY8/s320/hp-pavilion-hdx-entertainment-notebook.jpg" alt="" id="BLOGGER_PHOTO_ID_5200318759839483410" border="0" /></a><br /><a href="http://www.hp.com/">HP</a> (in association with <a href="http://www.buzzcorps.com/">Buzz Corps</a>)is promoting its latest 20.1&#8243; notebook i.e. <a href="http://h10010.www1.hp.com/wwpc/ca/en/ho/WF05a/12139188-78299199-78299212-78299212-78299212-80247383.html">The HP HDX</a><a href="http://h10010.www1.hp.com/wwpc/ca/en/ho/WF05a/12139188-78299199-78299212-78299212-78299212-80247383.html"> Entertainment Notebook</a> using a contest called &#8220;The 31 Days of Dragon&#8221;. Before I proceed further note that this contest is open to everyone from any part of the world.</p>
<p>Contest Info: There are 31 participating web sites, with the sponsorship of HP where HP is working with them to offer 31 prize packages &#8211; one a day for 31 days during a period from  09 May through 08 June 2008. Here are the details of the price package (which btw is worth well over USD 5000 in retail value):</p>
<ul>
<li>Display: 20.1″ diagonal WUXGA High-Definition HP Ultra Brightview Widescreen (1920×1200)  </li>
<li>Operating system: Windows Vista Ultimate (64-bit)  </li>
<li>Processor: Intel Core2 Extreme Processor X9000 (2.80GHz, 6MB L2 Cache, 800MHz FSB)  </li>
<li>Memory: 4GB DDR2 System Memory (2 Dimm)  </li>
<li>Graphics Card: 512MB NVIDIA GeForce 8800M GTS  </li>
<li>Personalization: HP Imprint Finish (Dragon) + Fingerprint Reader + Webcam + Microphone  </li>
<li>Networking: Intel(R) PRO/Wireless 4965AGN Network Connection and Bluetooth(TM)  </li>
<li>Hard Drive: 500GB 5400RPM SATA Dual Hard Drive (250GB x 2)  </li>
<li>Optical: Blu-Ray ROM with SuperMulti DVD+/-R/RW Double Layer  </li>
<li>TV &amp; Entertainment Experience: Integrated HP HDTV Hybrid TV Tuner and 4 Altec Lansing speakers + the HP Triple Bass Reflex subwoofer </li>
<li>Primary Battery: 9 Cell Lithium Ion Battery  </li>
<li>Productivity Applications: Microsoft Office 2007, Corel PaintShop Pro Photo X2, Corel Painter 4, Ulead VideoStudio 11.5 Plus  </li>
<li>Games: Viva Pinata, Microsoft Flight Simulator X, Microsoft Flight Simulator X Acceleration Expansion Pack, Gear of War  </li>
<li>Blu-Ray movies: The Pirates of the Caribbean Trilogy – The Curse of the Black Pearl, Dead Man&#8217;s Chest, At World&#8217;s End.</li>
</ul>
<p>And here is a list of websites <span class="style1">that are running/will run a seven day contest between the  following dates (begin &#8211; end)</span>:</p>
<p><span class="style1">13 May &#8211; 20 May <a mce_href="http://www.geekstogo.com/" href="http://www.geekstogo.com/"> www.geekstogo.com</a><br />14 May &#8211; 21 May <a mce_href="http://www.bink.nu/" href="http://bink.nu/">bink.nu</a></span> <span class="style1"><br />15 May &#8211; 22 May <a mce_href="http://www.mediablab.com/" href="http://www.mediablab.com/"> www.mediablab.com</a><br />16 May &#8211; 23 May <a href="http://www.last100.com/">www.last100.com</a></span> <span class="style1"><br />17 May &#8211; 24 May <a href="http://www.labnol.org/">www.labnol.org</a><br />18 May &#8211; 25 May <a mce_href="http://www.notebooks.com/" href="http://www.notebooks.com/"> www.notebooks.com</a><br />19 May &#8211; 26 May <a mce_href="http://www.slashdotreview.com/" href="http://www.slashdotreview.com/"> www.slashdotreview.com</a><br />20 May &#8211; 27 May <a mce_href="http://www.neowin.net/" href="http://www.neowin.net/"> www.neowin.net</a><br />21 May &#8211; 28 May <a mce_href="http://www.geek.com/" href="http://www.geek.com/"> www.geek.com</a><br />22 May &#8211; 29 Mau <a mce_href="http://www.lockergnome.com/" href="http://www.lockergnome.com/"> www.lockergnome.com</a></span> <span class="style1"><br />23 May &#8211; 30 May <a mce_href="http://www.planetx64.com/" href="http://www.planetx64.com/"> www.planetx64.com</a><br />24 May &#8211; 31 May <a mce_href="http://www.thegreenbutton.com/" href="http://www.thegreenbutton.com/"> www.thegreenbutton.com</a><br />25 May &#8211; 01 Jun <a mce_href="http://www.istartedsomething.com/" href="http://www.istartedsomething.com/"> www.istartedsomething.com</a><br />26 May &#8211; 02 Jun <a mce_href="http://www.bleepingcomputer.com/" href="http://www.bleepingcomputer.com/"> www.bleepingcomputer.com</a><br />27 May &#8211; 03 Jun <a mce_href="http://www.hardwaregeeks.com/" href="http://www.hardwaregeeks.com/"> www.hardwaregeeks.com</a><br />28 May &#8211; 04 Jun <a href="http://www.geeknewscentral.com/">www.geeknewscentral.com</a></span> <span class="style1"><br />29 May &#8211; 05 Jun <a mce_href="http://www.geekzone.co.nz/" href="http://www.geekzone.co.nz/"> www.geekzone.co.nz</a><br />30 May &#8211; 06 Jun <a mce_href="http://www.thetabletpc.net/" href="http://www.thetabletpc.net/"> www.thetabletpc.net</a><br />31 May &#8211; 07 Jun <a mce_href="http://www.gearlive.com/" href="http://www.gearlive.com/"> www.gearlive.com</a><br />01 Jun &#8211; 08 Jun <a mce_href="http://www.gottabemobile.com/" href="http://www.gottabemobile.com/"> www.gottabemobile.com</a></p>
<p>Some of the website site where the winners have already been declared are:</p>
<p></span><span class="style1">02 May &#8211; 09 May <a mce_href="http://www.absolutevista.com/" href="http://www.absolutevista.com/"> www.absolutevista.com</a><br />03 May &#8211; 10 May <a mce_href="http://www.arstechnica.com/" href="http://www.arstechnica.com/"> www.arstechnica.com</a><br />04 May &#8211; 11 May <a mce_href="http://www.osnn.net/" href="http://www.osnn.net/"> www.osnn.net</a><br />05 May &#8211; 12 May <a mce_href="http://www.jkontherun.com/" href="http://www.jkontherun.com/"> www.jkontherun.com</a><br />06 May &#8211; 13 May <a mce_href="http://digitalmediaphile.wordpress.com/" href="http://digitalmediaphile.wordpress.com/"> digitalmediaphile.wordpress.com</a></span> <span class="style1"><br />07 May &#8211; 14 May <a mce_href="http://www.bostonpocketpc.com/" href="http://www.bostonpocketpc.com/"> www.bostonpocketpc.com</a><br /></span><span class="style1"> 08 May &#8211; 15 May <a mce_href="http://www.the-gadgeteer.com/" href="http://www.the-gadgeteer.com/"> www.the-gadgeteer.com</a><br />09 May &#8211; 16 May <a href="http://www.thedigitallifestyle.com/">www.thedigitallifestyle.com</a> </span> <span class="style1"><br />10 May &#8211; 17 May <a mce_href="http://www.digitalhomethoughts.com/" href="http://www.digitalhomethoughts.com/"> www.digitalhomethoughts.com</a><br />11 May &#8211; 18 May <a mce_href="http://www.windows-now.com/" href="http://www.windows-now.com/"> www.windows-now.com</a><br />12 May &#8211; 19 May <a mce_href="http://www.windowsconnected.com/" href="http://www.windowsconnected.com/"> www.windowsconnected.com</a></span><br /><span class="style1"><br />So I suggest you to head over to these website and track them to win the super prize package. For details about the contest please visit <a href="http://www.31daysofthedragon.com/">here</a></span>. There is an RSS link and an OPML available so that you can track these sites conveniently.</p>
<p>In addition, most of these site have very good articles which techies and computer enthusiasts will definitely like. So go now and start participating, I am also competing <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>The post <a href="https://techblog.viveksanghi.com/2008/05/contest-suggestion-31-days-of-the-dragon/">Contest Suggestion: 31 Days of the Dragon</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">73</post-id>	</item>
		<item>
		<title>Poll result</title>
		<link>https://techblog.viveksanghi.com/2007/01/poll-result/</link>
					<comments>https://techblog.viveksanghi.com/2007/01/poll-result/#comments</comments>
		
		<dc:creator><![CDATA[Vivek Sanghi]]></dc:creator>
		<pubDate>Wed, 03 Jan 2007 20:46:00 +0000</pubDate>
				<category><![CDATA[blogging]]></category>
		<category><![CDATA[internet]]></category>
		<category><![CDATA[polls]]></category>
		<guid isPermaLink="false">https://techblog.viveksanghi.com/2007/01/poll-result/</guid>

					<description><![CDATA[<p>I had conducted the following poll a couple of months ago on my blog, but forgot to post the poll results after ending the poll. Better late than never:</p>
<p>The post <a href="https://techblog.viveksanghi.com/2007/01/poll-result/">Poll result</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>I had conducted the following poll a couple of months ago on my blog, but forgot to post the poll results after ending the poll. Better late than never:</p>
<p><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_XGtsagQTuUQ/RZwP2f2JNrI/AAAAAAAAAFQ/rJ9aLdaYUGI/s1600-h/pollresults.jpg"><img decoding="async" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://bp0.blogger.com/_XGtsagQTuUQ/RZwP2f2JNrI/AAAAAAAAAFQ/rJ9aLdaYUGI/s400/pollresults.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5015901513919837874" /></a></p>
<p>The post <a href="https://techblog.viveksanghi.com/2007/01/poll-result/">Poll result</a> appeared first on <a href="https://techblog.viveksanghi.com">Stubborn Fanatic - Tech Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techblog.viveksanghi.com/2007/01/poll-result/feed/</wfw:commentRss>
			<slash:comments>8</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">72</post-id>	</item>
	</channel>
</rss>