/** * Convert an annotation to an Atom entry * Logically this is part of the Annotation class, but different applications implement * that differently (OJS in particular), so it's here, but called through Annotation->toAtom(). */ function annotationToAtom(&$annotation, $tagHost, $servicePath, $strippedRoot = '') { $NS_XHTML = 'http://www.w3.org/1999/xhtml'; $sUserId = htmlspecialchars($annotation->getUserId()); $sUserName = htmlspecialchars($annotation->getUserName()); $sNote = htmlspecialchars($annotation->getNote()); $sQuote = htmlspecialchars($annotation->getQuote()); $sUrl = htmlspecialchars($annotation->getUrl()); $sLink = htmlspecialchars($annotation->getLink()); $sQuoteTitle = htmlspecialchars($annotation->getQuoteTitle()); $sQuoteAuthorId = htmlspecialchars($annotation->getQuoteAuthorId()); $sQuoteAuthorName = htmlspecialchars($annotation->getQuoteAuthorName()); $sAccess = htmlspecialchars($annotation->getAccess()); $sAction = htmlspecialchars($annotation->getAction()); // title for display to reader if ('edit' == $annotation->getAction()) { $title = "Edit to \"{$sQuoteTitle}\""; } elseif ($sNote) { $title = "Annotation of \"{$sQuoteTitle}\""; } else { $title = "Highlight of \"{$sQuoteTitle}\""; } // title and summary for display to reader if ($sNote && $sQuote) { $summary = $sNote . ": \"" . $sQuote . "\""; } elseif ($sNote) { $summary = $sNote; } else { $summary = $sQuote; } $s = " <entry>\n"; // Emit range in two formats: sequence for sorting, xpath for authority and speed $sequenceRange = $annotation->getSequenceRange(); if ($sequenceRange) { $s .= " <ptr:range format='sequence'>" . htmlspecialchars($sequenceRange->toString()) . "</ptr:range>\n"; } // Make 100% certain that the XPath expression contains no unsafe calls (e.g. to document()) $xpathRange = $annotation->getXPathRange(); if ($xpathRange && XPathPoint::isXPathSafe($xpathRange->start->getPathStr()) && XPathPoint::isXPathSafe($xpathRange->end->getPathStr())) { $s .= " <ptr:range format='xpath'>" . htmlspecialchars($xpathRange->toString()) . "</ptr:range>\n"; } $s .= " <ptr:access>{$sAccess}</ptr:access>\n" . " <ptr:action>{$sAction}</ptr:action>\n" . " <title>{$title}</title>\n"; // Use double quotes for some attributes because it's easier than passing ENT_QUOTES to // each call to htmlspecialchars $s .= " <link rel='self' type='application/xml' href=\"" . htmlspecialchars($servicePath . '/' . $annotation->getAnnotationId()) . "\"/>\n" . " <link rel='alternate' type='text/html' title=\"{$sQuoteTitle}\" href=\"{$sUrl}\"/>\n"; if ($annotation->getLink()) { $s .= " <link rel='related' type='text/html' title=\"{$sNote}\" href=\"{$sLink}\"/>\n"; } // TODO: Is this international-safe? I could use htmlsecialchars on it, but that might not match the // restrictions on IRIs. #GEOF# $s .= " <id>tag:{$tagHost}," . date('Y-m-d', $annotation->getCreated()) . ':annotation/' . $annotation->getAnnotationId() . "</id>\n" . " <updated>" . MarginaliaHelper::timeToIso($annotation->getModified()) . "</updated>\n" . " <ptr:created>" . MarginaliaHelper::timeToIso($annotation->getCreated()) . "</ptr:created>\n"; // Selected text as summary //echo " <summary>$summary</summary>\n"; // Author of the annotation $s .= " <author>\n" . " <name>{$sUserName}</name>\n" . " <ptr:userid>{$sUserId}</ptr:userid>\n" . " </author>\n"; // Contributor is the sources of the selected text $s .= " <contributor>\n" . " <name>{$sQuoteAuthorName}</name>\n" . " <ptr:userid>{$sQuoteAuthorId}</ptr:userid>\n" . " </contributor>\n"; // Content area /* This ends up making the client display ... for the note, which is confusing and wrong. if ( $sLink ) { if ( $sNote ) $sNote = "<a href=\"$sLink\">$sNote</a>"; else $sNote = "<a href=\"$sLink\">...</a>"; } */ $sQuote = "<q>{$sQuote}</q>"; if ('edit' == $annotation->getAction()) { if ($sNote) { $sNote = "<ins>{$sNote}</ins>"; } if ($sQuote) { $sQuote = "<del>{$sQuote}</del>"; } } $link = ''; if ($annotation->getLink()) { $link = htmlspecialchars($annotation->getLink()); if ($annotation->getLinkTitle()) { $link = "<cite><a href=\"{$link}\">" . htmlspecialchars($annotation->getLinkTitle()) . "</a></cite>"; } else { $link = "<a href=\"{$link}\">See Also</a>"; } } $s .= " <content type='xhtml'>\n" . " <div xmlns='{$NS_XHTML}' class='annotation'>\n" . "<p class='quote'>{$sQuote} ― <span class='quoteAuthor' title='{$sQuoteAuthorId}'>{$sQuoteAuthorName}</span> in " . "<cite><a href=\"{$sUrl}\">{$sQuoteTitle}</a></cite></p>\n" . "<p class='note'>{$sNote}</p>\n" . $link . " </div>\n" . " </content>\n" . " </entry>\n"; return $s; }
/** * Two ways to call: * - XPathPoint( '/p[2]/p[7]', 15, 3 ) * - XPathPoint( '/p[2]/p[7]/word(15,3)' ) */ function XPathPoint($xpathStr, $lines = null, $words = null, $chars = null) { $path = $xpathStr; while ($path != '') { $x = strrpos($path, '/'); $tail = FALSE === $x ? $path : substr($path, $x + 1); if (preg_match('/^(line|word|char)\\((\\d+)\\)$/', $tail, $matches)) { if ('line' == $matches[1]) { $this->lines = (int) $matches[2]; } elseif ('word' == $matches[1]) { $this->words = (int) $matches[2]; } elseif ('char' == $matches[1]) { $this->chars = (int) $matches[2]; } $path = FALSE === $x ? '' : substr($path, 0, $x); } else { break; } } if (XPathPoint::isXPathSafe($path)) { $this->path = $path; } else { $this->path = null; } if (null !== $lines) { $this->lines = $lines; } if (null !== $words) { $this->words = $words; } if (null !== $chars) { $this->chars = $chars; } // Treat zero as null for lines and words (but 0 is valid for chars) if ($this->lines == 0) { $this->lines = null; } if ($this->words == 0) { $this->words = null; } }
/** * Two ways to call: * - XPathPoint( '/p[2]/p[7]', 15, 3 ) * - XPathPoint( '/p[2]/p[7]/word(15,3)' ) */ function XPathPoint($xpathStr, $words = null, $chars = null) { if (preg_match('/^(.*)\\/word\\((\\d+)\\)\\/char\\((\\d+)\\)$/', $xpathStr, $matches)) { if (XPathPoint::isXPathSafe($matches[1])) { $this->path = $matches[1]; } // else // echo "Unsafe XPATH " + $matches[1] + "\n"; $this->words = (int) $matches[2]; $this->chars = (int) $matches[3]; } else { if (XPathPoint::isXPathSafe($xpathStr)) { $this->path = $xpathStr; } $this->words = $words; $this->chars = $chars; } }