mirror of https://github.com/adamdruppe/arsd.git
Merge branch 'master' of github.com:adamdruppe/arsd
This commit is contained in:
commit
bf20624b11
19
archive.d
19
archive.d
|
@ -27,6 +27,12 @@ struct TarFile {
|
|||
}
|
||||
+/
|
||||
|
||||
inout(char)[] upToZero(inout(char)[] a) {
|
||||
int i = 0;
|
||||
while(i < a.length && a[i]) i++;
|
||||
return a[0 .. i];
|
||||
}
|
||||
|
||||
|
||||
/++
|
||||
A header of a file in the archive. This represents the
|
||||
|
@ -56,8 +62,8 @@ struct TarFileHeader {
|
|||
const(char)[] filename() {
|
||||
import core.stdc.string;
|
||||
if(filenamePrefix_[0])
|
||||
return filenamePrefix_[0 .. strlen(filenamePrefix_.ptr)] ~ fileName_[0 .. strlen(fileName_.ptr)];
|
||||
return fileName_[0 .. strlen(fileName_.ptr)];
|
||||
return upToZero(filenamePrefix_[]) ~ upToZero(fileName_[]);
|
||||
return upToZero(fileName_[]);
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -112,8 +118,8 @@ bool processTar(
|
|||
|
||||
if(*bytesRemainingOnCurrentFile) {
|
||||
bool isNew = *bytesRemainingOnCurrentFile == header.size();
|
||||
if(*bytesRemainingOnCurrentFile < 512) {
|
||||
handleData(header, isNew, true, dataBuffer[0 .. *bytesRemainingOnCurrentFile]);
|
||||
if(*bytesRemainingOnCurrentFile <= 512) {
|
||||
handleData(header, isNew, true, dataBuffer[0 .. cast(size_t) *bytesRemainingOnCurrentFile]);
|
||||
*bytesRemainingOnCurrentFile = 0;
|
||||
} else {
|
||||
handleData(header, isNew, false, dataBuffer[]);
|
||||
|
@ -123,6 +129,8 @@ bool processTar(
|
|||
*header = *(cast(TarFileHeader*) dataBuffer.ptr);
|
||||
auto s = header.size();
|
||||
*bytesRemainingOnCurrentFile = s;
|
||||
if(header.type() == TarFileType.directory)
|
||||
handleData(header, true, false, null);
|
||||
if(s == 0 && header.type == TarFileType.normal)
|
||||
return false;
|
||||
}
|
||||
|
@ -1877,7 +1885,7 @@ static ELzma2State Lzma2Dec_UpdateState(CLzma2Dec *p, Byte b)
|
|||
{
|
||||
switch(p.state)
|
||||
{
|
||||
default: assert(0);
|
||||
default: return ELzma2State.LZMA2_STATE_ERROR;
|
||||
case ELzma2State.LZMA2_STATE_CONTROL:
|
||||
p.control = b;
|
||||
if (p.control == 0)
|
||||
|
@ -1928,7 +1936,6 @@ static ELzma2State Lzma2Dec_UpdateState(CLzma2Dec *p, Byte b)
|
|||
return ELzma2State.LZMA2_STATE_DATA;
|
||||
}
|
||||
}
|
||||
return ELzma2State.LZMA2_STATE_ERROR;
|
||||
}
|
||||
|
||||
static void LzmaDec_UpdateWithUncompressed(CLzmaDec *p, Byte *src, SizeT size)
|
||||
|
|
48
cgi.d
48
cgi.d
|
@ -1586,6 +1586,7 @@ class Cgi {
|
|||
immutable(ubyte)[] data;
|
||||
|
||||
void rdo(const(ubyte)[] d) {
|
||||
//import std.stdio; writeln(d);
|
||||
sendAll(ir.source, d);
|
||||
}
|
||||
|
||||
|
@ -3370,6 +3371,8 @@ void cgiMainImpl(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxC
|
|||
try {
|
||||
fun(cgi);
|
||||
cgi.close();
|
||||
if(cgi.websocketMode)
|
||||
closeConnection = true;
|
||||
} catch(ConnectionException ce) {
|
||||
closeConnection = true;
|
||||
} catch(Throwable t) {
|
||||
|
@ -3665,6 +3668,8 @@ void doThreadHttpConnection(CustomCgi, alias fun)(Socket connection) {
|
|||
try {
|
||||
fun(cgi);
|
||||
cgi.close();
|
||||
if(cgi.websocketMode)
|
||||
closeConnection = true;
|
||||
} catch(ConnectionException ce) {
|
||||
// broken pipe or something, just abort the connection
|
||||
closeConnection = true;
|
||||
|
@ -3947,6 +3952,7 @@ class BufferedInputRange {
|
|||
// we might have to grow the buffer
|
||||
if(minBytesToSettleFor > underlyingBuffer.length || view.length == underlyingBuffer.length) {
|
||||
if(allowGrowth) {
|
||||
import std.stdio; writeln("growth");
|
||||
auto viewStart = view.ptr - underlyingBuffer.ptr;
|
||||
size_t growth = 4096;
|
||||
// make sure we have enough for what we're being asked for
|
||||
|
@ -3959,7 +3965,7 @@ class BufferedInputRange {
|
|||
}
|
||||
|
||||
do {
|
||||
auto freeSpace = underlyingBuffer[underlyingBuffer.ptr - view.ptr + view.length .. $];
|
||||
auto freeSpace = underlyingBuffer[view.ptr - underlyingBuffer.ptr + view.length .. $];
|
||||
try_again:
|
||||
auto ret = source.receive(freeSpace);
|
||||
if(ret == Socket.ERROR) {
|
||||
|
@ -3981,6 +3987,7 @@ class BufferedInputRange {
|
|||
return;
|
||||
}
|
||||
|
||||
//import std.stdio; writeln(view.ptr); writeln(underlyingBuffer.ptr); writeln(view.length, " ", ret, " = ", view.length + ret);
|
||||
view = underlyingBuffer[view.ptr - underlyingBuffer.ptr .. view.length + ret];
|
||||
} while(view.length < minBytesToSettleFor);
|
||||
}
|
||||
|
@ -3992,6 +3999,7 @@ class BufferedInputRange {
|
|||
/// You do not need to call this if you always want to wait for more data when you
|
||||
/// consume some.
|
||||
ubyte[] consume(size_t bytes) {
|
||||
//import std.stdio; writeln("consuime ", bytes, "/", view.length);
|
||||
view = view[bytes > $ ? $ : bytes .. $];
|
||||
if(view.length == 0) {
|
||||
view = underlyingBuffer[0 .. 0]; // go ahead and reuse the beginning
|
||||
|
@ -4639,18 +4647,13 @@ version(cgi_with_websocket) {
|
|||
// note: this blocks
|
||||
WebSocketMessage recv() {
|
||||
// FIXME: should we automatically handle pings and pongs?
|
||||
assert(!cgi.idlol.empty());
|
||||
if(cgi.idlol.empty())
|
||||
throw new Exception("remote side disconnected");
|
||||
cgi.idlol.popFront(0);
|
||||
|
||||
WebSocketMessage message;
|
||||
|
||||
auto info = cgi.idlol.front();
|
||||
|
||||
// FIXME: read should prolly take the whole range so it can request more if needed
|
||||
// read should also go ahead and consume the range
|
||||
message = WebSocketMessage.read(info);
|
||||
|
||||
cgi.idlol.consume(info.length);
|
||||
message = WebSocketMessage.read(cgi.idlol);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
@ -4700,7 +4703,7 @@ version(cgi_with_websocket) {
|
|||
WebSocket acceptWebsocket(Cgi cgi) {
|
||||
assert(!cgi.closed);
|
||||
assert(!cgi.outputtedResponseData);
|
||||
cgi.setResponseStatus("101 Web Socket Protocol Handshake");
|
||||
cgi.setResponseStatus("101 Switching Protocols");
|
||||
cgi.header("Upgrade: WebSocket");
|
||||
cgi.header("Connection: upgrade");
|
||||
|
||||
|
@ -4834,7 +4837,15 @@ version(cgi_with_websocket) {
|
|||
cgi.flush();
|
||||
}
|
||||
|
||||
static WebSocketMessage read(ubyte[] d) {
|
||||
static WebSocketMessage read(BufferedInputRange ir) {
|
||||
|
||||
auto d = ir.front();
|
||||
while(d.length < 2) {
|
||||
ir.popFront();
|
||||
d = ir.front();
|
||||
}
|
||||
auto start = d;
|
||||
|
||||
WebSocketMessage msg;
|
||||
assert(d.length >= 2);
|
||||
|
||||
|
@ -4882,7 +4893,11 @@ version(cgi_with_websocket) {
|
|||
d = d[4 .. $];
|
||||
}
|
||||
|
||||
msg.data = d[0 .. $];
|
||||
//if(d.length < msg.realLength) {
|
||||
|
||||
//}
|
||||
msg.data = d[0 .. msg.realLength];
|
||||
d = d[msg.realLength .. $];
|
||||
|
||||
if(msg.masked) {
|
||||
// let's just unmask it now
|
||||
|
@ -4896,6 +4911,8 @@ version(cgi_with_websocket) {
|
|||
}
|
||||
}
|
||||
|
||||
ir.consume(start.length - d.length);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
@ -6427,6 +6444,7 @@ void runAddonServer(EIS)(string localListenerName, EIS eis) if(is(EIS : EventIoS
|
|||
import core.sys.posix.poll;
|
||||
}
|
||||
|
||||
version(linux)
|
||||
eis.epoll_fd = epoll_fd;
|
||||
|
||||
auto acceptOp = allocateIoOp(sock, IoOp.Read, 0, null);
|
||||
|
@ -9069,11 +9087,11 @@ bool apiDispatcher()(Cgi cgi) {
|
|||
}
|
||||
+/
|
||||
/*
|
||||
Copyright: Adam D. Ruppe, 2008 - 2019
|
||||
License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
|
||||
Copyright: Adam D. Ruppe, 2008 - 2020
|
||||
License: [http://www.boost.org/LICENSE_1_0.txt|Boost License 1.0].
|
||||
Authors: Adam D. Ruppe
|
||||
|
||||
Copyright Adam D. Ruppe 2008 - 2019.
|
||||
Copyright Adam D. Ruppe 2008 - 2020.
|
||||
Distributed under the Boost Software License, Version 1.0.
|
||||
(See accompanying file LICENSE_1_0.txt or copy at
|
||||
http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
|
|
@ -5,26 +5,27 @@ module arsd.declarativeloader;
|
|||
|
||||
import std.range;
|
||||
|
||||
// @VariableLength indicates the value is saved in a MIDI like format
|
||||
// @BigEndian, @LittleEndian
|
||||
// @NumBytes!Field or @NumElements!Field controls length of embedded arrays
|
||||
// @Tagged!Field indicates a tagged union. Each struct within should have @Tag(X) which is a value of Field
|
||||
// @MustBe() causes it to throw if not the given value
|
||||
|
||||
// @NotSaved indicates a struct member that is not actually saved in the file
|
||||
|
||||
///
|
||||
enum BigEndian;
|
||||
///
|
||||
enum LittleEndian;
|
||||
/// @VariableLength indicates the value is saved in a MIDI like format
|
||||
enum VariableLength;
|
||||
/// @NumBytes!Field or @NumElements!Field controls length of embedded arrays
|
||||
struct NumBytes(alias field) {}
|
||||
/// ditto
|
||||
struct NumElements(alias field) {}
|
||||
/// @Tagged!Field indicates a tagged union. Each struct within should have @Tag(X) which is a value of Field
|
||||
struct Tagged(alias field) {}
|
||||
struct TagStruct(T) { T t; }
|
||||
/// ditto
|
||||
auto Tag(T)(T t) {
|
||||
return TagStruct!T(t);
|
||||
}
|
||||
enum NotSaved;
|
||||
struct TagStruct(T) { T t; }
|
||||
struct MustBeStruct(T) { T t; }
|
||||
/// The marked field is not in the actual file
|
||||
enum NotSaved;
|
||||
/// Insists the field must be a certain value, like for magic numbers
|
||||
auto MustBe(T)(T t) {
|
||||
return MustBeStruct!T(t);
|
||||
}
|
||||
|
@ -65,10 +66,18 @@ union N(ty) {
|
|||
ubyte[ty.sizeof] bytes;
|
||||
}
|
||||
|
||||
// input range of ubytes...
|
||||
/// input range of ubytes...
|
||||
int loadFrom(T, Range)(ref T t, auto ref Range r, bool assumeBigEndian = false) {
|
||||
int bytesConsumed;
|
||||
string currentItem;
|
||||
|
||||
import std.conv;
|
||||
scope(failure)
|
||||
throw new Exception(T.stringof ~ "." ~ currentItem ~ " trouble " ~ to!string(t));
|
||||
|
||||
ubyte next() {
|
||||
if(r.empty)
|
||||
throw new Exception(T.stringof ~ "." ~ currentItem ~ " trouble " ~ to!string(t));
|
||||
auto bfr = r.front;
|
||||
r.popFront;
|
||||
bytesConsumed++;
|
||||
|
@ -77,6 +86,7 @@ int loadFrom(T, Range)(ref T t, auto ref Range r, bool assumeBigEndian = false)
|
|||
|
||||
bool endianness = bigEndian!T(assumeBigEndian);
|
||||
static foreach(memberName; __traits(allMembers, T)) {{
|
||||
currentItem = memberName;
|
||||
static if(is(typeof(__traits(getMember, T, memberName)))) {
|
||||
alias f = __traits(getMember, T, memberName);
|
||||
alias ty = typeof(f);
|
||||
|
@ -114,11 +124,17 @@ int loadFrom(T, Range)(ref T t, auto ref Range r, bool assumeBigEndian = false)
|
|||
|
||||
auto tag = __traits(getMember, t, tagField);
|
||||
// find the child of the union matching the tag...
|
||||
bool found = false;
|
||||
static foreach(um; __traits(allMembers, ty)) {
|
||||
if(tag == getTag!(__traits(getMember, ty, um))) {
|
||||
bytesConsumed += loadFrom(__traits(getMember, __traits(getMember, t, memberName), um), r, endianness);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
import std.format;
|
||||
throw new Exception(format("found unknown union tag %s at %s", tag, t));
|
||||
}
|
||||
} else static if(is(ty == E[], E)) {
|
||||
static foreach(attr; __traits(getAttributes, f)) {
|
||||
static if(is(attr == NumBytes!Field, alias Field))
|
||||
|
@ -160,9 +176,19 @@ int loadFrom(T, Range)(ref T t, auto ref Range r, bool assumeBigEndian = false)
|
|||
}
|
||||
} else {
|
||||
while(numElementsRemaining) {
|
||||
//import std.stdio; writeln(memberName);
|
||||
E piece;
|
||||
auto by = loadFrom(piece, r, endianness);
|
||||
numElementsRemaining--;
|
||||
|
||||
// such a filthy hack, needed for Java's mistake though :(
|
||||
static if(__traits(compiles, piece.takesTwoSlots())) {
|
||||
if(piece.takesTwoSlots()) {
|
||||
__traits(getMember, t, memberName) ~= piece;
|
||||
numElementsRemaining--;
|
||||
}
|
||||
}
|
||||
|
||||
bytesConsumed += by;
|
||||
__traits(getMember, t, memberName) ~= piece;
|
||||
}
|
||||
|
|
269
dom.d
269
dom.d
|
@ -1,8 +1,7 @@
|
|||
// FIXME: add classList
|
||||
// FIXME: add classList. it is a live list and removes whitespace and duplicates when you use it.
|
||||
// FIXME: xml namespace support???
|
||||
// FIXME: add matchesSelector - standard name is `matches`. also `closest` walks up to find the parent that matches
|
||||
// FIXME: https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML
|
||||
// FIXME: appendChild should not fail if the thing already has a parent; it should just automatically remove it per standard.
|
||||
// FIXME: parentElement is parentNode that skips DocumentFragment etc but will be hard to work in with my compatibility...
|
||||
|
||||
// FIXME: the scriptable list is quite arbitrary
|
||||
|
||||
|
@ -34,6 +33,21 @@
|
|||
|
||||
If you want it to stand alone, just always use the `Document.parseUtf8`
|
||||
function or the constructor that takes a string.
|
||||
|
||||
Symbol_groups:
|
||||
|
||||
core_functionality =
|
||||
|
||||
These members provide core functionality. The members on these classes
|
||||
will provide most your direct interaction.
|
||||
|
||||
bonus_functionality =
|
||||
|
||||
These provide additional functionality for special use cases.
|
||||
|
||||
implementations =
|
||||
|
||||
These provide implementations of other functionality.
|
||||
+/
|
||||
module arsd.dom;
|
||||
|
||||
|
@ -79,6 +93,7 @@ bool isConvenientAttribute(string name) {
|
|||
|
||||
|
||||
/// The main document interface, including a html parser.
|
||||
/// Group: core_functionality
|
||||
class Document : FileResource {
|
||||
/// Convenience method for web scraping. Requires [arsd.http2] to be
|
||||
/// included in the build as well as [arsd.characterencodings].
|
||||
|
@ -1185,37 +1200,70 @@ class Document : FileResource {
|
|||
if( is(SomeElementType : Element))
|
||||
out(ret) { assert(ret !is null); }
|
||||
body {
|
||||
return root.requireSelector!(SomeElementType)(selector, file, line);
|
||||
auto e = cast(SomeElementType) querySelector(selector);
|
||||
if(e is null)
|
||||
throw new ElementNotFoundException(SomeElementType.stringof, selector, this.root, file, line);
|
||||
return e;
|
||||
}
|
||||
|
||||
final MaybeNullElement!SomeElementType optionSelector(SomeElementType = Element)(string selector, string file = __FILE__, size_t line = __LINE__)
|
||||
if(is(SomeElementType : Element))
|
||||
{
|
||||
return root.optionSelector!(SomeElementType)(selector, file, line);
|
||||
auto e = cast(SomeElementType) querySelector(selector);
|
||||
return MaybeNullElement!SomeElementType(e);
|
||||
}
|
||||
|
||||
|
||||
/// ditto
|
||||
@scriptable
|
||||
Element querySelector(string selector) {
|
||||
return root.querySelector(selector);
|
||||
// see comment below on Document.querySelectorAll
|
||||
auto s = Selector(selector);//, !loose);
|
||||
foreach(ref comp; s.components)
|
||||
if(comp.parts.length && comp.parts[0].separation == 0)
|
||||
comp.parts[0].separation = -1;
|
||||
foreach(e; s.getMatchingElementsLazy(this.root))
|
||||
return e;
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/// ditto
|
||||
@scriptable
|
||||
Element[] querySelectorAll(string selector) {
|
||||
return root.querySelectorAll(selector);
|
||||
// In standards-compliant code, the document is slightly magical
|
||||
// in that it is a pseudoelement at top level. It should actually
|
||||
// match the root as one of its children.
|
||||
//
|
||||
// In versions of dom.d before Dec 29 2019, this worked because
|
||||
// querySelectorAll was willing to return itself. With that bug fix
|
||||
// (search "arbitrary id asduiwh" in this file for associated unittest)
|
||||
// this would have failed. Hence adding back the root if it matches the
|
||||
// selector itself.
|
||||
//
|
||||
// I'd love to do this better later.
|
||||
|
||||
auto s = Selector(selector);//, !loose);
|
||||
foreach(ref comp; s.components)
|
||||
if(comp.parts.length && comp.parts[0].separation == 0)
|
||||
comp.parts[0].separation = -1;
|
||||
return s.getMatchingElements(this.root);
|
||||
}
|
||||
|
||||
/// ditto
|
||||
@scriptable
|
||||
deprecated("use querySelectorAll instead")
|
||||
Element[] getElementsBySelector(string selector) {
|
||||
return root.getElementsBySelector(selector);
|
||||
}
|
||||
|
||||
/// ditto
|
||||
@scriptable
|
||||
Element[] getElementsByTagName(string tag) {
|
||||
return root.getElementsByTagName(tag);
|
||||
}
|
||||
|
||||
/// ditto
|
||||
@scriptable
|
||||
Element[] getElementsByClassName(string tag) {
|
||||
return root.getElementsByClassName(tag);
|
||||
}
|
||||
|
@ -1395,6 +1443,7 @@ class Document : FileResource {
|
|||
}
|
||||
|
||||
/// This represents almost everything in the DOM.
|
||||
/// Group: core_functionality
|
||||
class Element {
|
||||
/// Returns a collection of elements by selector.
|
||||
/// See: [Document.opIndex]
|
||||
|
@ -2181,10 +2230,37 @@ class Element {
|
|||
}
|
||||
|
||||
/// a more standards-compliant alias for getElementsBySelector
|
||||
@scriptable
|
||||
Element[] querySelectorAll(string selector) {
|
||||
return getElementsBySelector(selector);
|
||||
}
|
||||
|
||||
/// If the element matches the given selector. Previously known as `matchesSelector`.
|
||||
@scriptable
|
||||
bool matches(string selector) {
|
||||
/+
|
||||
bool caseSensitiveTags = true;
|
||||
if(parentDocument && parentDocument.loose)
|
||||
caseSensitiveTags = false;
|
||||
+/
|
||||
|
||||
Selector s = Selector(selector);
|
||||
return s.matchesElement(this);
|
||||
}
|
||||
|
||||
/// Returns itself or the closest parent that matches the given selector, or null if none found
|
||||
/// See_also: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
|
||||
@scriptable
|
||||
Element closest(string selector) {
|
||||
Element e = this;
|
||||
while(e !is null) {
|
||||
if(e.matches(selector))
|
||||
return e;
|
||||
e = e.parentNode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns elements that match the given CSS selector
|
||||
|
||||
|
@ -2543,11 +2619,17 @@ class Element {
|
|||
}
|
||||
|
||||
|
||||
/// Appends the given element to this one. The given element must not have a parent already.
|
||||
/++
|
||||
Appends the given element to this one. If it already has a parent, it is removed from that tree and moved to this one.
|
||||
|
||||
See_also: https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild
|
||||
|
||||
History:
|
||||
Prior to 1 Jan 2020 (git tag v4.4.1 and below), it required that the given element must not have a parent already. This was in violation of standard, so it changed the behavior to remove it from the existing parent and instead move it here.
|
||||
+/
|
||||
Element appendChild(Element e)
|
||||
in {
|
||||
assert(e !is null);
|
||||
assert(e.parentNode is null, e.parentNode.toString);
|
||||
}
|
||||
out (ret) {
|
||||
assert((cast(DocumentFragment) this !is null) || (e.parentNode is this), e.toString);// e.parentNode ? e.parentNode.toString : "null");
|
||||
|
@ -2555,6 +2637,9 @@ class Element {
|
|||
assert(e is ret);
|
||||
}
|
||||
body {
|
||||
if(e.parentNode !is null)
|
||||
e.parentNode.removeChild(e);
|
||||
|
||||
selfClosed = false;
|
||||
e.parentNode = this;
|
||||
e.parentDocument = this.parentDocument;
|
||||
|
@ -3410,6 +3495,7 @@ class Element {
|
|||
|
||||
// FIXME: since Document loosens the input requirements, it should probably be the sub class...
|
||||
/// Specializes Document for handling generic XML. (always uses strict mode, uses xml mime type and file header)
|
||||
/// Group: core_functionality
|
||||
class XmlDocument : Document {
|
||||
this(string data) {
|
||||
contentType = "text/xml; charset=utf-8";
|
||||
|
@ -3427,6 +3513,7 @@ import std.string;
|
|||
/* domconvenience follows { */
|
||||
|
||||
/// finds comments that match the given txt. Case insensitive, strips whitespace.
|
||||
/// Group: core_functionality
|
||||
Element[] findComments(Document document, string txt) {
|
||||
return findComments(document.root, txt);
|
||||
}
|
||||
|
@ -3446,6 +3533,7 @@ Element[] findComments(Element element, string txt) {
|
|||
}
|
||||
|
||||
/// An option type that propagates null. See: [Element.optionSelector]
|
||||
/// Group: implementations
|
||||
struct MaybeNullElement(SomeElementType) {
|
||||
this(SomeElementType ele) {
|
||||
this.element = ele;
|
||||
|
@ -3479,6 +3567,7 @@ struct MaybeNullElement(SomeElementType) {
|
|||
/++
|
||||
A collection of elements which forwards methods to the children.
|
||||
+/
|
||||
/// Group: implementations
|
||||
struct ElementCollection {
|
||||
///
|
||||
this(Element e) {
|
||||
|
@ -3577,6 +3666,7 @@ struct ElementCollection {
|
|||
|
||||
|
||||
/// this puts in operators and opDispatch to handle string indexes and properties, forwarding to get and set functions.
|
||||
/// Group: implementations
|
||||
mixin template JavascriptStyleDispatch() {
|
||||
///
|
||||
string opDispatch(string name)(string v = null) if(name != "popFront") { // popFront will make this look like a range. Do not want.
|
||||
|
@ -3604,6 +3694,7 @@ mixin template JavascriptStyleDispatch() {
|
|||
/// A proxy object to do the Element class' dataset property. See Element.dataset for more info.
|
||||
///
|
||||
/// Do not create this object directly.
|
||||
/// Group: implementations
|
||||
struct DataSet {
|
||||
///
|
||||
this(Element e) {
|
||||
|
@ -3627,6 +3718,7 @@ struct DataSet {
|
|||
}
|
||||
|
||||
/// Proxy object for attributes which will replace the main opDispatch eventually
|
||||
/// Group: implementations
|
||||
struct AttributeSet {
|
||||
///
|
||||
this(Element e) {
|
||||
|
@ -3654,6 +3746,7 @@ struct AttributeSet {
|
|||
/// for style, i want to be able to set it with a string like a plain attribute,
|
||||
/// but also be able to do properties Javascript style.
|
||||
|
||||
/// Group: implementations
|
||||
struct ElementStyle {
|
||||
this(Element parent) {
|
||||
_element = parent;
|
||||
|
@ -3810,6 +3903,7 @@ import std.range;
|
|||
Document implements this interface with type = text/html (see Document.contentType for more info)
|
||||
and data = document.toString, so you can return Documents anywhere web.d expects FileResources.
|
||||
+/
|
||||
/// Group: bonus_functionality
|
||||
interface FileResource {
|
||||
/// the content-type of the file. e.g. "text/html; charset=utf-8" or "image/png"
|
||||
@property string contentType() const;
|
||||
|
@ -3821,10 +3915,12 @@ interface FileResource {
|
|||
|
||||
|
||||
///.
|
||||
/// Group: bonus_functionality
|
||||
enum NodeType { Text = 3 }
|
||||
|
||||
|
||||
/// You can use this to do an easy null check or a dynamic cast+null check on any element.
|
||||
/// Group: core_functionality
|
||||
T require(T = Element, string file = __FILE__, int line = __LINE__)(Element e) if(is(T : Element))
|
||||
in {}
|
||||
out(ret) { assert(ret !is null); }
|
||||
|
@ -3837,6 +3933,7 @@ body {
|
|||
|
||||
|
||||
///.
|
||||
/// Group: core_functionality
|
||||
class DocumentFragment : Element {
|
||||
///.
|
||||
this(Document _parentDocument) {
|
||||
|
@ -3887,6 +3984,7 @@ class DocumentFragment : Element {
|
|||
///
|
||||
/// The output parameter can be given to append to an existing buffer. You don't have to
|
||||
/// pass one; regardless, the return value will be usable for you, with just the data encoded.
|
||||
/// Group: core_functionality
|
||||
string htmlEntitiesEncode(string data, Appender!string output = appender!string(), bool encodeNonAscii = true) {
|
||||
// if there's no entities, we can save a lot of time by not bothering with the
|
||||
// decoding loop. This check cuts the net toString time by better than half in my test.
|
||||
|
@ -3939,11 +4037,13 @@ string htmlEntitiesEncode(string data, Appender!string output = appender!string(
|
|||
}
|
||||
|
||||
/// An alias for htmlEntitiesEncode; it works for xml too
|
||||
/// Group: core_functionality
|
||||
string xmlEntitiesEncode(string data) {
|
||||
return htmlEntitiesEncode(data);
|
||||
}
|
||||
|
||||
/// This helper function is used for decoding html entities. It has a hard-coded list of entities and characters.
|
||||
/// Group: core_functionality
|
||||
dchar parseEntity(in dchar[] entity) {
|
||||
switch(entity[1..$-1]) {
|
||||
case "quot":
|
||||
|
@ -5441,6 +5541,7 @@ import std.stdio;
|
|||
/// This takes a string of raw HTML and decodes the entities into a nice D utf-8 string.
|
||||
/// By default, it uses loose mode - it will try to return a useful string from garbage input too.
|
||||
/// Set the second parameter to true if you'd prefer it to strictly throw exceptions on garbage input.
|
||||
/// Group: core_functionality
|
||||
string htmlEntitiesDecode(string data, bool strict = false) {
|
||||
// this check makes a *big* difference; about a 50% improvement of parse speed on my test.
|
||||
if(data.indexOf("&") == -1) // all html entities begin with &
|
||||
|
@ -5521,6 +5622,7 @@ string htmlEntitiesDecode(string data, bool strict = false) {
|
|||
return cast(string) a; // assumeUnique is actually kinda slow, lol
|
||||
}
|
||||
|
||||
/// Group: implementations
|
||||
abstract class SpecialElement : Element {
|
||||
this(Document _parentDocument) {
|
||||
super(_parentDocument);
|
||||
|
@ -5538,6 +5640,7 @@ abstract class SpecialElement : Element {
|
|||
}
|
||||
|
||||
///.
|
||||
/// Group: implementations
|
||||
class RawSource : SpecialElement {
|
||||
///.
|
||||
this(Document _parentDocument, string s) {
|
||||
|
@ -5570,6 +5673,7 @@ class RawSource : SpecialElement {
|
|||
string source;
|
||||
}
|
||||
|
||||
/// Group: implementations
|
||||
abstract class ServerSideCode : SpecialElement {
|
||||
this(Document _parentDocument, string type) {
|
||||
super(_parentDocument);
|
||||
|
@ -5599,6 +5703,7 @@ abstract class ServerSideCode : SpecialElement {
|
|||
}
|
||||
|
||||
///.
|
||||
/// Group: implementations
|
||||
class PhpCode : ServerSideCode {
|
||||
///.
|
||||
this(Document _parentDocument, string s) {
|
||||
|
@ -5612,6 +5717,7 @@ class PhpCode : ServerSideCode {
|
|||
}
|
||||
|
||||
///.
|
||||
/// Group: implementations
|
||||
class AspCode : ServerSideCode {
|
||||
///.
|
||||
this(Document _parentDocument, string s) {
|
||||
|
@ -5625,6 +5731,7 @@ class AspCode : ServerSideCode {
|
|||
}
|
||||
|
||||
///.
|
||||
/// Group: implementations
|
||||
class BangInstruction : SpecialElement {
|
||||
///.
|
||||
this(Document _parentDocument, string s) {
|
||||
|
@ -5664,6 +5771,7 @@ class BangInstruction : SpecialElement {
|
|||
}
|
||||
|
||||
///.
|
||||
/// Group: implementations
|
||||
class QuestionInstruction : SpecialElement {
|
||||
///.
|
||||
this(Document _parentDocument, string s) {
|
||||
|
@ -5704,6 +5812,7 @@ class QuestionInstruction : SpecialElement {
|
|||
}
|
||||
|
||||
///.
|
||||
/// Group: implementations
|
||||
class HtmlComment : SpecialElement {
|
||||
///.
|
||||
this(Document _parentDocument, string s) {
|
||||
|
@ -5747,6 +5856,7 @@ class HtmlComment : SpecialElement {
|
|||
|
||||
|
||||
///.
|
||||
/// Group: implementations
|
||||
class TextNode : Element {
|
||||
public:
|
||||
///.
|
||||
|
@ -5862,6 +5972,7 @@ class TextNode : Element {
|
|||
*/
|
||||
|
||||
///.
|
||||
/// Group: implementations
|
||||
class Link : Element {
|
||||
|
||||
///.
|
||||
|
@ -6000,6 +6111,7 @@ class Link : Element {
|
|||
}
|
||||
|
||||
///.
|
||||
/// Group: implementations
|
||||
class Form : Element {
|
||||
|
||||
///.
|
||||
|
@ -6250,6 +6362,7 @@ class Form : Element {
|
|||
import std.conv;
|
||||
|
||||
///.
|
||||
/// Group: implementations
|
||||
class Table : Element {
|
||||
|
||||
///.
|
||||
|
@ -6489,6 +6602,7 @@ class Table : Element {
|
|||
}
|
||||
|
||||
/// Represents a table row element - a <tr>
|
||||
/// Group: implementations
|
||||
class TableRow : Element {
|
||||
///.
|
||||
this(Document _parentDocument) {
|
||||
|
@ -6501,6 +6615,7 @@ class TableRow : Element {
|
|||
}
|
||||
|
||||
/// Represents anything that can be a table cell - <td> or <th> html.
|
||||
/// Group: implementations
|
||||
class TableCell : Element {
|
||||
///.
|
||||
this(Document _parentDocument, string _tagName) {
|
||||
|
@ -6537,6 +6652,7 @@ class TableCell : Element {
|
|||
|
||||
|
||||
///.
|
||||
/// Group: implementations
|
||||
class MarkupException : Exception {
|
||||
|
||||
///.
|
||||
|
@ -6546,6 +6662,7 @@ class MarkupException : Exception {
|
|||
}
|
||||
|
||||
/// This is used when you are using one of the require variants of navigation, and no matching element can be found in the tree.
|
||||
/// Group: implementations
|
||||
class ElementNotFoundException : Exception {
|
||||
|
||||
/// type == kind of element you were looking for and search == a selector describing the search.
|
||||
|
@ -6560,6 +6677,7 @@ class ElementNotFoundException : Exception {
|
|||
/// The html struct is used to differentiate between regular text nodes and html in certain functions
|
||||
///
|
||||
/// Easiest way to construct it is like this: `auto html = Html("<p>hello</p>");`
|
||||
/// Group: core_functionality
|
||||
struct Html {
|
||||
/// This string holds the actual html. Use it to retrieve the contents.
|
||||
string source;
|
||||
|
@ -7160,6 +7278,7 @@ int intFromHex(string hex) {
|
|||
}
|
||||
|
||||
auto part = parts[0];
|
||||
//writeln("checking ", part, " against ", start, " with ", part.separation);
|
||||
switch(part.separation) {
|
||||
default: assert(0);
|
||||
case -1:
|
||||
|
@ -7229,19 +7348,24 @@ int intFromHex(string hex) {
|
|||
}
|
||||
|
||||
/++
|
||||
Represents a parsed CSS selector.
|
||||
Represents a parsed CSS selector. You never have to use this directly, but you can if you know it is going to be reused a lot to avoid a bit of repeat parsing.
|
||||
|
||||
See_Also:
|
||||
[Element.querySelector]
|
||||
[Element.querySelectorAll]
|
||||
[Document.querySelector]
|
||||
[Document.querySelectorAll]
|
||||
$(LIST
|
||||
* [Element.querySelector]
|
||||
* [Element.querySelectorAll]
|
||||
* [Element.matches]
|
||||
* [Element.closest]
|
||||
* [Document.querySelector]
|
||||
* [Document.querySelectorAll]
|
||||
)
|
||||
+/
|
||||
/// Group: core_functionality
|
||||
struct Selector {
|
||||
SelectorComponent[] components;
|
||||
string original;
|
||||
/++
|
||||
Parses the selector string and returns the usable structure.
|
||||
Parses the selector string and constructs the usable structure.
|
||||
+/
|
||||
this(string cssSelector) {
|
||||
components = parseSelectorString(cssSelector);
|
||||
|
@ -7336,9 +7460,25 @@ int intFromHex(string hex) {
|
|||
if(e is null) return false;
|
||||
Element where = e;
|
||||
int lastSeparation = -1;
|
||||
foreach(part; retro(parts)) {
|
||||
|
||||
// writeln("matching ", where, " with ", part, " via ", lastSeparation);
|
||||
auto lparts = parts;
|
||||
|
||||
if(parts.length && parts[0].separation > 0) {
|
||||
// if it starts with a non-trivial separator, inject
|
||||
// a "*" matcher to act as a root. for cases like document.querySelector("> body")
|
||||
// which implies html
|
||||
|
||||
// there is probably a MUCH better way to do this.
|
||||
auto dummy = SelectorPart.init;
|
||||
dummy.tagNameFilter = "*";
|
||||
dummy.separation = 0;
|
||||
lparts = dummy ~ lparts;
|
||||
}
|
||||
|
||||
foreach(part; retro(lparts)) {
|
||||
|
||||
// writeln("matching ", where, " with ", part, " via ", lastSeparation);
|
||||
// writeln(parts);
|
||||
|
||||
if(lastSeparation == -1) {
|
||||
if(!part.matchElement(where))
|
||||
|
@ -7346,6 +7486,7 @@ int intFromHex(string hex) {
|
|||
} else if(lastSeparation == 0) { // generic parent
|
||||
// need to go up the whole chain
|
||||
where = where.parentNode;
|
||||
|
||||
while(where !is null) {
|
||||
if(part.matchElement(where))
|
||||
break;
|
||||
|
@ -7476,6 +7617,8 @@ int intFromHex(string hex) {
|
|||
|
||||
if(current.isCleanSlateExceptSeparation()) {
|
||||
current.tagNameFilter = token;
|
||||
// default thing, see comment under "*" below
|
||||
if(current.separation == -1) current.separation = 0;
|
||||
} else {
|
||||
// if it was already set, we must see two thingies
|
||||
// separated by whitespace...
|
||||
|
@ -7488,6 +7631,10 @@ int intFromHex(string hex) {
|
|||
switch(token) {
|
||||
case "*":
|
||||
current.tagNameFilter = "*";
|
||||
// the idea here is if we haven't actually set a separation
|
||||
// yet (e.g. the > operator), it should assume the generic
|
||||
// whitespace (descendant) mode to avoid matching self with -1
|
||||
if(current.separation == -1) current.separation = 0;
|
||||
break;
|
||||
case " ":
|
||||
// If some other separation has already been set,
|
||||
|
@ -7520,16 +7667,20 @@ int intFromHex(string hex) {
|
|||
break;
|
||||
case "[":
|
||||
state = State.ReadingAttributeSelector;
|
||||
if(current.separation == -1) current.separation = 0;
|
||||
break;
|
||||
case ".":
|
||||
state = State.ReadingClass;
|
||||
if(current.separation == -1) current.separation = 0;
|
||||
break;
|
||||
case "#":
|
||||
state = State.ReadingId;
|
||||
if(current.separation == -1) current.separation = 0;
|
||||
break;
|
||||
case ":":
|
||||
case "::":
|
||||
state = State.ReadingPseudoClass;
|
||||
if(current.separation == -1) current.separation = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -8542,18 +8693,6 @@ private bool isSimpleWhite(dchar c) {
|
|||
return c == ' ' || c == '\r' || c == '\n' || c == '\t';
|
||||
}
|
||||
|
||||
/*
|
||||
Copyright: Adam D. Ruppe, 2010 - 2019
|
||||
License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
|
||||
Authors: Adam D. Ruppe, with contributions by Nick Sabalausky, Trass3r, and ketmar among others
|
||||
|
||||
Copyright Adam D. Ruppe 2010-2019.
|
||||
Distributed under the Boost Software License, Version 1.0.
|
||||
(See accompanying file LICENSE_1_0.txt or copy at
|
||||
http://www.boost.org/LICENSE_1_0.txt)
|
||||
*/
|
||||
|
||||
|
||||
unittest {
|
||||
// Test for issue #120
|
||||
string s = `<html>
|
||||
|
@ -8570,3 +8709,73 @@ unittest {
|
|||
s2.indexOf("bubbles") < s2.indexOf("giggles"),
|
||||
"paragraph order incorrect:\n" ~ s2);
|
||||
}
|
||||
|
||||
unittest {
|
||||
// test for suncarpet email dec 24 2019
|
||||
// arbitrary id asduiwh
|
||||
auto document = new Document("<html>
|
||||
<head>
|
||||
<meta charset=\"utf-8\"></meta>
|
||||
<title>Element.querySelector Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id=\"foo\">
|
||||
<div>Foo</div>
|
||||
<div>Bar</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>");
|
||||
|
||||
auto doc = document;
|
||||
|
||||
assert(doc.querySelectorAll("div div").length == 2);
|
||||
assert(doc.querySelector("div").querySelectorAll("div").length == 2);
|
||||
assert(doc.querySelectorAll("> html").length == 0);
|
||||
assert(doc.querySelector("head").querySelectorAll("> title").length == 1);
|
||||
assert(doc.querySelector("head").querySelectorAll("> meta[charset]").length == 1);
|
||||
|
||||
|
||||
assert(doc.root.matches("html"));
|
||||
assert(!doc.root.matches("nothtml"));
|
||||
assert(doc.querySelector("#foo > div").matches("div"));
|
||||
assert(doc.querySelector("body > #foo").matches("#foo"));
|
||||
|
||||
assert(doc.root.querySelectorAll(":root > body").length == 0); // the root has no CHILD root!
|
||||
assert(doc.querySelectorAll(":root > body").length == 1); // but the DOCUMENT does
|
||||
assert(doc.querySelectorAll(" > body").length == 1); // should mean the same thing
|
||||
assert(doc.root.querySelectorAll(" > body").length == 1); // the root of HTML has this
|
||||
assert(doc.root.querySelectorAll(" > html").length == 0); // but not this
|
||||
}
|
||||
|
||||
unittest {
|
||||
// based on https://developer.mozilla.org/en-US/docs/Web/API/Element/closest example
|
||||
auto document = new Document(`<article>
|
||||
<div id="div-01">Here is div-01
|
||||
<div id="div-02">Here is div-02
|
||||
<div id="div-03">Here is div-03</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>`, true, true);
|
||||
|
||||
auto el = document.getElementById("div-03");
|
||||
assert(el.closest("#div-02").id == "div-02");
|
||||
assert(el.closest("div div").id == "div-03");
|
||||
assert(el.closest("article > div").id == "div-01");
|
||||
assert(el.closest(":not(div)").tagName == "article");
|
||||
|
||||
assert(el.closest("p") is null);
|
||||
assert(el.closest("p, div") is el);
|
||||
}
|
||||
|
||||
/*
|
||||
Copyright: Adam D. Ruppe, 2010 - 2020
|
||||
License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
|
||||
Authors: Adam D. Ruppe, with contributions by Nick Sabalausky, Trass3r, and ketmar among others
|
||||
|
||||
Copyright Adam D. Ruppe 2010-2020.
|
||||
Distributed under the Boost Software License, Version 1.0.
|
||||
(See accompanying file LICENSE_1_0.txt or copy at
|
||||
http://www.boost.org/LICENSE_1_0.txt)
|
||||
*/
|
||||
|
||||
|
||||
|
|
2
dub.json
2
dub.json
|
@ -3,7 +3,7 @@
|
|||
"targetType": "library",
|
||||
"importPaths": ["."],
|
||||
"sourceFiles": ["package.d"],
|
||||
"description": "Subpackage collection for web, database, terminal ui, gui, scripting, and more.",
|
||||
"description": "Subpackage collection for web, database, terminal ui, gui, scripting, and more with a commitment to long-term compatibility and stability.",
|
||||
"authors": ["Adam D. Ruppe"],
|
||||
"license":"BSL-1.0",
|
||||
"dependencies": {
|
||||
|
|
|
@ -468,6 +468,6 @@ class HtmlConverter {
|
|||
///
|
||||
string htmlToText(string html, bool wantWordWrap = true, int wrapAmount = 74) {
|
||||
auto converter = new HtmlConverter();
|
||||
return converter.convert(html, true, wrapAmount);
|
||||
return converter.convert(html, wantWordWrap, wrapAmount);
|
||||
}
|
||||
|
||||
|
|
5
rpc.d
5
rpc.d
|
@ -6,6 +6,11 @@ module arsd.rpc;
|
|||
1) integrate with arsd.eventloop
|
||||
2) make it easy to use with other processes; pipe to a process and talk to it that way. perhaps with shared memory too?
|
||||
3) extend the serialization capabilities
|
||||
|
||||
|
||||
@Throws!(List, Of, Exceptions)
|
||||
classes are also RPC proxied
|
||||
stdin/out/err also redirected
|
||||
*/
|
||||
|
||||
///+ //example usage
|
||||
|
|
|
@ -459,7 +459,7 @@
|
|||
I live in the eastern United States, so I will most likely not be around at night in
|
||||
that US east timezone.
|
||||
|
||||
License: Copyright Adam D. Ruppe, 2011-2017. Released under the Boost Software License.
|
||||
License: Copyright Adam D. Ruppe, 2011-2020. Released under the Boost Software License.
|
||||
|
||||
Building documentation: You may wish to use the `arsd.ddoc` file from my github with
|
||||
building the documentation for simpledisplay yourself. It will give it a bit more style.
|
||||
|
|
Loading…
Reference in New Issue