Merge branch 'master' of github.com:adamdruppe/arsd

This commit is contained in:
Adam D. Ruppe 2020-01-17 23:18:18 -05:00
commit bf20624b11
10 changed files with 2189 additions and 322 deletions

View File

@ -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
View File

@ -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)

View File

@ -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
View File

@ -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)
*/

View File

@ -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": {

View File

@ -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);
}

1263
http2.d

File diff suppressed because it is too large Load Diff

853
jni.d

File diff suppressed because it is too large Load Diff

5
rpc.d
View File

@ -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

View File

@ -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.