[fix] slick-router

This commit is contained in:
Andy Bunce 2024-04-03 12:12:32 +01:00
parent 0659567f36
commit dcd18dd3d5
18 changed files with 618 additions and 28 deletions

63
src/lib/bookpages.xqm Normal file
View file

@ -0,0 +1,63 @@
xquery version '3.1';
(:~ describe book page numbers as sequence of ranges, similar to PDF pagelabels
@author quodatum
:)
module namespace bookpages = 'urn:bookpages';
(:~ Invisible-xml grammar to parse custom pagelabel representation :)
declare variable $bookpages:grammar:="
book: pagecount,'#',range,(-',', range)*.
pagecount:['0'-'9']+.
range: s,from?,s,type,s,prefix?,s,offset?.
@from: ['0'-'9']+. { pageIndex }
@type: ['C'|'D'|'R'|'r'|'A'|'a'|'w'].
@prefix: -':',~[',']+.
@offset: -'@',['0'-'9']+.
-s: ([Zs]; #9; #a; #d)*. {Optional whitespace}
";
(:~
page number range in given style
:)
declare function bookpages:span($type as xs:string,$length as xs:integer,$first as xs:integer)
as xs:string*{
let $r:=$first to $first+$length
return switch ($type)
case "D" return $r!format-integer(.,"1")
case "r" return $r!format-integer(.,"i")
case "R" return $r!format-integer(.,"I")
case "C" return "Cover"
default return $r!format-integer(.,$type)
};
(:~ pagelabels from text:)
declare function bookpages:expand($pages as xs:string)
as xs:string*{
let $x:=bookpages:parse($pages)
let $last:=head($x)=>xs:integer()
return hof:until(
function($m){ empty($m?ranges) or count($m?result)eq $last },
function($m){
let $range:=head($m?ranges)=>trace("SS")
let $start:=if($range/@offset)then xs:integer($range/@offset) else 1
let $end:=($m?ranges[2]/xs:integer(@from)-1) otherwise $last
let $length:=$end -count($m?result)-1
let $span:=bookpages:span($range/@type,$length,$start)
let $span:=if($range/@prefix)then $span!concat($range/@prefix,.) else $span
return map {
'ranges': tail($m?ranges),
'result': ($m?result, $span)
}},
(: initial input = grammar ranges :)
map { 'ranges': tail($x) , 'result': () }
)?result
};
(:~ parse pagenumber description to xml :)
declare function bookpages:parse($pages as xs:string)
as element(range)*{
invisible-xml($bookpages:grammar)($pages)/*
};

View file

@ -2,12 +2,12 @@ xquery version '3.1';
(:~
pdfbox 3.0 https://pdfbox.apache.org/ BaseX 10.7+ interface library,
requires pdfbox jar on classpath
3.02 required tested with pdfbox-app-3.0.2-20240121.184204-66.jar
3.02+ required tested with pdfbox-app-3.0.2.jar
@see https://repository.apache.org/content/groups/snapshots/org/apache/pdfbox/pdfbox-app/3.0.2-SNAPSHOT/
@javadoc https://javadoc.io/static/org.apache.pdfbox/pdfbox/3.0.0/
:)
module namespace pdfbox="urn:expkg-zone58:pdfbox:3";
module namespace pdfbox="urn:expkg-zone58:pdfbox3";
declare namespace Loader ="java:org.apache.pdfbox.Loader";
declare namespace PDFTextStripper = "java:org.apache.pdfbox.text.PDFTextStripper";
@ -96,7 +96,7 @@ as map(*)*{
};
(: return bookmark info for children of $outlineItem as seq of maps :)
declare function pdfbox:outline($doc,$outlineItem )
declare function pdfbox:outline($doc as item(),$outlineItem as item()?)
as map(*)*
{
let $find:=hof:until(
@ -143,7 +143,7 @@ as map(*)
}
};
declare function pdfbox:outx($page,$document)
declare function pdfbox:outx($page ,$document)
{
let $currentPage := PDOutlineItem:findDestinationPage($page,$document)
let $pageNumber := pdfbox:pageIndex($currentPage,$document)

View file

@ -1,16 +1,16 @@
xquery version '3.1';
(:~ look for pagenos in pdf text
pagenos:page-report($doc )=>pagenos:inverted-map()
pdfscrape:page-report($doc )=>pdfscrape:inverted-map()
:)
module namespace pagenos = 'urn:pageno';
import module namespace pdfbox="urn:expkg-zone58:pdfbox:3" at "pdfbox3.xqm";
module namespace pdfscrape = 'urn:pdfscrape';
import module namespace pdfbox="urn:expkg-zone58:pdfbox3" at "pdfbox3.xqm";
(: look for possible page number in first/last line of page text
@todo last line and roman
1=Number system ( D=decimal, R=Roman)
2=Side L=left,R=right
:)
declare variable $pagenos:pats:=map{
declare variable $pdfscrape:pats:=map{
"DL": "^([1-9][0-9]*).*",
"DR": ".*[^0-9]([1-9][0-9]*)$",
"RL": "^([ivxlc]+).*",
@ -18,47 +18,56 @@ declare variable $pagenos:pats:=map{
};
(: page-reports for all pages :)
declare function pagenos:page-report($doc as item())
declare function pdfscrape:page-report($doc as item())
as element(page)*{
let $count:=pdfbox:page-count($doc)=>trace("Pages: ")
return (0 to $count -1)!pagenos:page-report($doc,.)
return (1 to $count )!pdfscrape:page-report($doc,.)
};
(: page-report for given page :)
declare function pagenos:page-report($doc as item(), $page as xs:integer)
declare function pdfscrape:page-report($doc as item(), $page as xs:integer)
as element(page){
let $txt:=pdfbox:getText($doc,$page)
let $line1:=substring-before($txt,file:line-separator())
let $fn:=function($acc,$this){ $acc otherwise pagenos:line-report($this,$line1)}
let $found:=map:keys($pagenos:pats)=>fold-left( (),$fn)
let $fn:=function($acc,$this){ $acc otherwise pdfscrape:line-report($this,$line1)}
let $found:=map:keys($pdfscrape:pats)=>fold-left( (),$fn)
return <page index="{ $page }">{ $found, $line1 }</page>
};
(: empty or attributes created by matching $style with $line1 :)
declare function pagenos:line-report($style as xs:string, $line1 as xs:string)
declare function pdfscrape:line-report($style as xs:string, $line1 as xs:string)
as attribute(*)*{
if(matches($line1,$pagenos:pats?($style)))
if(matches($line1,$pdfscrape:pats?($style)))
then (
attribute {"style"} { substring($style,1,1) } ,(: 1st key:)
attribute {"LR"} { substring($style,2,1) } ,(: 2nd key:)
attribute {"number"} { replace($line1,$pagenos:pats?($style),"$1") }
attribute {"number"} { replace($line1,$pdfscrape:pats?($style),"$1") }
)
};
(:~ keys are parsed pageno values are pageindices where found:)
declare function pagenos:inverted-map($pages as element(page)*)
declare function pdfscrape:inverted-map($pages as element(page)*)
as map(*) {
$pages[@number]!map:entry(string(@number),string(@index))
=>map:merge(map{"duplicates":"combine"})
};
(:~ %match
$l page labels
:)
declare function pdfscrape:score($l as xs:string*,$report as element(page)*)
{
let $s:=$report!(if(@number)then string(@number) else "")
let $match:= for-each-pair($l,$s,function($l,$s){if($s eq "")then 0 else if ($s eq $l)then 1 else -1})
return round(sum($match) div count($l) *100,0)
};
(:~ convert roman to integer, zero if invalid
@see https://joewiz.org/2021/05/30/converting-roman-numerals-with-xquery-xslt/
:)
declare function pagenos:decode-roman-numeral($roman-numeral as xs:string)
declare function pdfscrape:decode-roman-numeral($roman-numeral as xs:string)
as xs:integer{
$roman-numeral => upper-case() => pagenos:characters()
$roman-numeral => upper-case() => characters()
=> for-each(map { "M": 1000, "D": 500, "C": 100, "L": 50, "X": 10, "V": 5, "I": 1 })
=> fold-right([0,0], function($number,$accumulator) {
if ($number lt $accumulator?2)
@ -67,8 +76,3 @@ as xs:integer{
=> array:head()
};
(:~ xpath 4:)
declare function pagenos:characters($value as xs:string?)
as xs:string*{
fn:string-to-codepoints($value) ! fn:codepoints-to-string(.)
};

26
src/scratch/abc.xq Normal file
View file

@ -0,0 +1,26 @@
(: test use of pageIndex :)
import module namespace pdfbox="urn:expkg-zone58:pdfbox:3" at "../src/lib/pdfbox3.xqm";
import module namespace pagenos = 'urn:pageno' at "../src/lib/pageno.xqm";
declare variable $base:=file:base-dir();
declare function local:go($doc,$pdf as element(pdf)){
let $range:=$pdf/@pages/tokenize(.,"")
let $start:=$range[1]
let $end:=if(count($range) eq 1) then $range[1] else $range[2]
return ``[ `{$start}` ;;; `{ $end }` ]``
};
let $src:="257107---Book_File-Web_PDF_9798400691218_486731.pdf"=>file:resolve-path($base)
let $doc:=pdfbox:open($src)
let $labels:= pdfbox:getPageLabels($doc)
let $pdfs:=doc("pdfs\chunks-docbook.xml")/chunks/pdf
for $pdf in $pdfs
let $range:=$pdf/@pages/tokenize(.,"")
let $start:=$range[1]
let $end:=if(count($range) eq 1) then $range[1] else $range[2]
let $startIndex:=index-of($labels,$start)
let $endIndex:=index-of($labels,$end)
return if(exists($startIndex) and exists($endIndex))
then $pdf/@pages || " " || $startIndex || ":" || $endIndex
(: pdfbox:extract($doc,$startIndex,$endIndex,file:resolve-path($pdf/@fileref,$base)) :)
else $pdf/@pages

20
src/scratch/nos.xq Normal file
View file

@ -0,0 +1,20 @@
(:~ describe book page numbering :)
import module namespace pdfbox="urn:expkg-zone58:pdfbox3" at "../lib/pdfbox3.xqm";
import module namespace bookpages="urn:bookpages" at "../lib/bookpages.xqm";
import module namespace pdfscrape="urn:pdfscrape" at "../lib/pdfscrape.xqm";
declare variable $base:="C:\Users\mrwhe\Desktop\1e\";
declare variable $tests:=map{
"simple":"20#C,R,7D",
"set\2-6-2\A5267C": "1037#C,r,28D,520r:V2,526D@493",
"gpg-book\2-3\A3581C-TRD": "848#C,r:Vol1:,28D,400r:Vol2:,438D@401"
};
let $pdf:=pdfbox:open("C:\Users\mrwhe\Desktop\1e\set\2-6-2\A5267C\257273---Book_File-Web_PDF_9798400612572_486638.pdf")
let $l:=pdfbox:getPageLabels($pdf)
let $index:=bookpages:expand($tests?"set\2-6-2\A5267C")
return pdfscrape:score($l,pdfscrape:page-report($pdf))

View file

@ -1,7 +1,7 @@
(: PDFBOX experiments
:)
import module namespace pdfbox="urn:expkg-zone58:pdfbox:3" at "../lib/pdfbox3.xqm";
import module namespace pdfbox="urn:expkg-zone58:pdfbox3" at "../lib/pdfbox3.xqm";
declare variable $samples:= map{

87
src/webapp/pdf/app.xqm Normal file
View file

@ -0,0 +1,87 @@
(:~
: Common RESTXQ access points.
:
: @author Christian Grün, BaseX Team 2005-23, BSD License
:)
module namespace pdf = 'pdf/common';
(:~
: Redirects to the start page.
: @return redirection
:)
declare
%rest:path('/pdf')
function pdf:home() as element() {
web:forward('/pdf/static/index.html')
};
declare
%rest:path('/pdf/{$file=.+}')
function pdf:spa($file)as element(){
pdf:home()
};
(:~ list slugs :)
declare
%rest:path('/pdf/api/sources')
%output:method("json")
%output:json("format=xquery")
function pdf:apt()
{
let $base:="C:/Users/mrwhe/git/expkg-zone58/pdfbox/data/"
let $d:="1e/"
let $f:=file:list($base || $d,true(),"*.pdf")[not(contains(.,"\outputs\"))]
return map{
"count": count($f),
"items": array{$f!map{"id":position(),"name":.}}
}
};
(:~
: Returns a file.
: @param $file file or unknown path
: @return rest binary data
:)
declare
%rest:path('/pdf/static/{$file=.+}')
%output:method('basex')
%perm:allow('public')
function pdf:file(
$file as xs:string
) as item()+ {
let $path := file:base-dir() || "static/" || $file
return if(file:exists($path))
then (
web:response-header(
map { 'media-type': web:content-type($path) },
map { 'Cache-Control': 'max-age=3600,public', 'Content-Length': file:size($path) }
),
file:read-binary($path)
)else
web:forward("/pdf/api/404")
};
(:~
: Shows a 'page not found' error.
: @param $path path to unknown page
: @return page
:)
declare
%rest:path('/pdf/api/404')
%output:method('html')
function pdf:unknown(
$path as xs:string
) as element(*) {
<tr>
<td>
<h2>Page not found:</h2>
<ul>
<li>Page: dba/{ $path }</li>
<li>Method: { request:method() }</li>
</ul>
</td>
</tr>
};

15
src/webapp/pdf/readme.md Normal file
View file

@ -0,0 +1,15 @@
Uses
* https://www.npmjs.com/package/slick-router
<style>table, th, td {
border: 1px solid black;
border-collapse: collapse;
}</style>
<table >
<caption>Sample table</caption>
<tr>
<td>Foo</td>
</tr>
<tr>
<td>3</td>
</tr>
</table>

View file

@ -0,0 +1,53 @@
/* extracted from https://vuejs.org/v2/guide/transitions.html */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
/* Enter and leave animations can use different */
/* durations and timing functions. */
.slide-fade-enter-active {
transition: all 0.3s ease;
}
.slide-fade-leave-active {
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter, .slide-fade-leave-to {
transform: translateX(1000px);
opacity: 0;
}
.bounce-enter {
opacity: 0;
}
.bounce-enter-active {
animation: bounce-in 0.8s;
}
.bounce-leave-active {
animation: bounce-in 0.8s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
80% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
router-outlet > * {
display: block;
}

View file

@ -0,0 +1,139 @@
import { withRouterLinks } from 'https://unpkg.com/slick-router@2.5.0/middlewares/router-links.js'
class ApplicationView extends withRouterLinks(HTMLElement) {
constructor() {
super()
this.addEventListener('change', e => {
if (e.target.matches('#animation-type')) {
const animation = e.target.value
if (animation) {
this.outlet.setAttribute('animation', e.target.value)
} else {
this.outlet.removeAttribute('animation')
}
}
})
}
connectedCallback() {
super.connectedCallback()
this.innerHTML = `
<div class='App'>
<div class='App-header'>
<h1>Application</h1>
<ul class='Nav' routerlinks>
<li class='Nav-item'><a route="home" >Home</a></li>
<li class='Nav-item'><a route="messages">Messages</a></li>
<li class='Nav-item'><a route="profile.index" param-user="scrobblemuch">Profile</a></li>
<li class='Nav-item'><a route="settings" >Settings</a></li>
</ul>
</div>
<router-outlet animation="fade"></router-outlet>
<div class="App-footer">
<div>
Animation
<select id="animation-type">
<option value="">None</option>
<option value="fade" selected>Fade</option>
<option value="slide-fade">Slide Fade</option>
<option value="bounce">Bounce</option>
</select>
</div>
</div>
</div>
`
this.outlet = this.querySelector('router-outlet')
}
}
customElements.define('application-view', ApplicationView)
class HomeView extends withRouterLinks(HTMLElement) {
connectedCallback() {
super.connectedCallback()
this.innerHTML = `
<div class='Home' routerlinks>
<h2>Tweets</h2>
<div class='Tweet'>
<div class='Tweet-author'>
<a route="profile.index" param-user="dan_abramov">Dan Abramov @dan_abramov</a>
</div>
<div class='Tweet-time'>12m12 minutes ago</div>
<div class='Tweet-content'>Another use case for \`this.context\` I think might be valid: forms. They're too painful right now.</div>
</div>
<div class='Tweet'>
<div class='Tweet-author'>
<a route="profile.index" param-user="afanasjevas">Eduardas Afanasjevas @afanasjevas</a>
</div>
<div class='Tweet-time'>12m12 minutes ago</div>
<div class='Tweet-content'>I just published What will Datasmoothie bring to the analytics startup landscape? https://medium.com/@afanasjevas/what-will-datasmoothie-bring-to-the-analytics-startup-landscape-f7dab70d75c3?source=tw-81c4e81fe6f8-1427630532296</div>
</div>
<div class='Tweet'>
<div class='Tweet-author'>
<a route="profile.index" param-user="LNUGorg">LNUG @LNUGorg</a>
</div>
<div class='Tweet-time'>52m52 minutes ago</div>
<div class='Tweet-content'> new talks uploaded on our YouTube page - check them out http://bit.ly/1yoXSAO</div>
</div>
</div>
`
}
}
customElements.define('home-view', HomeView)
class MessagesView extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<div class='Messages'>
<h2>Messages</h2>
<p>You have no direct messages</p>
</div>
`
}
}
customElements.define('messages-view', MessagesView)
class SettingsView extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<div class='Messages'>
<h2>Sett</h2>
<p>You have no direct messages</p>
</div>
`
}
}
customElements.define('settings-view', SettingsView)
class ProfileView extends HTMLElement {
static get outlet() {
return '.Container'
}
connectedCallback() {
this.innerHTML = `
<div class='Profile'>
<div class='Container'></div>
</div>
`
}
}
customElements.define('profile-view', ProfileView)
class ProfileIndexView extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<div class='ProfileIndex'>
<h2>${this.$route.params.user} profile</h2>
</div>
`
}
}
customElements.define('profile-index-view', ProfileIndexView)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<base href="/pdf/static/"/>
<title>PDFS453 </title>
<link rel="icon" href="favicon.png">
<script type="module" src="index.js"></script>
<link rel="stylesheet" href="styles.css" />
<link rel="stylesheet" href="animations.css" />
</head>
<body>
</body>
</html>

View file

@ -0,0 +1,41 @@
import { Router } from 'https://unpkg.com/slick-router@2.5.0/slick-router.js?module'
import { wc } from 'https://unpkg.com/slick-router@2.5.0/middlewares/wc.js'
import { events } from 'https://unpkg.com/slick-router@2.5.0/middlewares/events.js'
import { routerLinks } from 'https://unpkg.com/slick-router@2.5.0/middlewares/router-links.js'
import { AnimatedOutlet } from 'https://unpkg.com/slick-router@2.5.0/components/animated-outlet.js'
import './components.js'
customElements.define('router-outlet', AnimatedOutlet)
// create the router
const router = new Router({
pushState: true,
log: true
})
// provide your route map
// in this particular case we configure components by its tag name
router.map(route => {
route('application', { path: '/pdf/', component: 'application-view' }, () => {
route('home', { path: '', component: 'home-view' })
route('messages', { component: 'messages-view' })
route('status', { path: ':user/status/:id' })
route('profile', { path: 'profile/:user', component: 'profile-view' }, () => {
route('profile.index', { path: '', component: 'profile-index-view' })
route('profile.lists')
route('profile.edit')
})
route('settings',{path: 'settings',component:'settings-view'})
})
})
// install middleware that will handle transitions
router.use(wc)
router.use(routerLinks)
router.use(events)
// start listening to browser's location bar changes
router.listen()

View file

@ -0,0 +1,75 @@
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,700');
body,
html {
margin: 0;
padding: 0;
font-family: 'Open Sans', sans-serif;
}
.App {
width: 800px;
margin: 0 auto 20px auto;
}
.App-header {
border-bottom: 1px solid #eee;
}
.App-header .active {
font-weight: bolder;
}
.App-footer {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
padding-bottom: 20px;
text-align: center;
background-color: bisque;
}
.App h1 {
display: inline-block;
}
.Nav {
display: inline-block;
}
.Nav-item {
list-style: none;
display: inline-block;
}
.Nav-item a {
padding: 10px;
}
.Tweet {
border: 1px solid #eee;
border-radius: 3px;
padding: 10px;
border-bottom: none;
}
.Tweet:last-child {
border-bottom: 1px solid #eee;
}
.Tweet-author {
font-weight: bold;
display: inline-block;
}
.Tweet-time {
color: #888;
display: inline-block;
margin-left: 20px;
font-size: 12px;
}
router-outlet > * {
display: block;
}

View file

@ -0,0 +1,47 @@
customElements.define('summary-display',
class extends HTMLElement {
constructor() {
super();
const template = document.getElementById('summary-display-template');
const templateContent = template.content;
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(templateContent.cloneNode(true));
const items = Array.from(this.querySelectorAll('li'));
const descriptions = Array.from(this.querySelectorAll('p'));
items.forEach(item => {
handleClick(item);
});
function handleClick(item) {
item.addEventListener('click', function() {
items.forEach(item => {
item.style.backgroundColor = 'white';
});
descriptions.forEach(description => {
updateDisplay(description, item);
});
});
}
function updateDisplay(description, item) {
description.removeAttribute('slot');
if(description.getAttribute('data-name') === item.textContent) {
description.setAttribute('slot', 'choice');
item.style.backgroundColor = '#bad0e4';
}
}
const slots = this.shadowRoot.querySelectorAll('slot');
slots[1].addEventListener('slotchange', function(e) {
const nodes = slots[1].assignedNodes();
console.log(`Element in Slot "${slots[1].name}" changed to "${nodes[0].outerHTML}".`);
});
}
}
);