How do I process metadata on titles in asciidoctor? - asciidoctor

I'm using asciidoctor to generate a static site for an upcoming event in my hometown, and we have a list of hotels and places of interest for out of town guests. Each location has a position on the map, a type (hotel, restaurant, ect.) and whether their a sponsor of the event.
I've read the docs on Block Processors and this seems to be the best way to do it, but I can't figure out how to tag a title with metadata that can be processed this way.
Currently, we have the descriptions of each location in the document like this
== Locations
[[Fancy_Hotel]]
=== Fancy Hotel
[location, 55, 73, hotel, sponsor]
A nice hotel in the middle of town
[[Chain_Restaurant]]
=== Chain Breakfast Restaurant
[location, 98, 16, restaurant]
A good place for food
and I read the metadata when building the page, then populate the map with clickable links which navigate to the appropriate entry. The following code works, but I'd like to move the metadata above the title if possible, so I'm not relying on parent so aggressively.
require 'asciidoctor'
require 'asciidoctor/extensions'
$Locations = Array.new
def make_map_svg locs
# Do some magic here
locs.each {|a| puts a}
end
class LocationBlock < Asciidoctor::Extensions::BlockProcessor
use_dsl
named :location
on_context :paragraph
name_positional_attributes ['x', 'y', 'type', 'sponsor']
def process parent, reader, attrs
x = ((attrs.delete 'x') || 0).to_i
y = ((attrs.delete 'y') || 0).to_i
type = attrs.delete 'type'
sponsor = (attrs.delete 'sponsor') == 'sponsor'
$Locations.push({
x:x,
y:y,
type:type,
sponsor:sponsor,
title:parent.title,
parent.id
})
create_paragraph parent, reader.lines, attrs, {}
end
end
Asciidoctor::Extensions.register do
block LocationBlock
end
Asciidoctor.convert_file "./Locations.adoc"
make_map_svg $Locations
The make_map_svg function works fine, the issue is figuring out how to make the metadata entries less hacky.

You might think of the location data as metadata, but processing it with a block processor implies that it is not metadata; blocks occupy a location in Asciidoctor's AST and are expected to occupy a position in the output; there should be something rendered at that location.
Titles are special in that they define both the text of the section/document title, but also a container for the title and the section's child elements. If you use a block processor, it must follow the title, or it is not enclosed in the container with the title and other child elements. Navigating the element structure from your [location ...] block to the entitled section would be more challenging than just using parent.
If the location data is truly metadata, in that you do not expect anything to render where [location ...] exists, you might consider implementing your logic as a preprocessor, which could read the location information from a comment in the lines of your source.
One advantage of using the preprocessor approach to parse comments is that anyone using your Asciidoctor source would see the same rendering (aside from CSS), whether they had your extension installed/active or not.

Related

Is this efficient way to use Nim ref data structure?

I'm keeping in memory huge list of companies, and need to do lots of operations of getting individual company.
Like getting individual company "Microsoft" by its symbol "MSFT".
Would the data structure below be a proper way to model that? There should be no copy-by-value of the whole list or map.
It is ok if the individual company would be copied by value.
import tables
type
Company = object
name: string
symbol: string
description: string
CompaniesRef = ref object
list: seq[Company]
map: Table[string, Company]
# Cached data structure to keep thousands of different companies
var cached_companies: CompaniesRef
proc companies(): CompaniesRef =
if cached_companies == nil:
# Here will be a proper code of loading companies into the
# CompaniesRef data structure
cached_companies = CompaniesRef()
cached_companies
# Lots of operations of getting a specific company from the list
# or from the map by its symbol
for i in 1..1000:
# it's ok if individual company will be copied by value,
# but the whole list should be passed by reference
let company1 = companies().list[0].name
# it's ok if individual company will be copied by value
# but the whole map should be passed by reference
let company2 = companies().map["MSFT"]
That global structure should be fine as it is, an object reference is just a memory managed pointer, so passing its reference around only copies the memory address. Unless you are going to do something with that pointer, why not create it as a global? Hiding it behind a proc call reeks of the I'm-afraid-of-globals-but-can't-live-without-them-singleton pattern.
let companies = CompaniesRef()
With regards to the contents of the structure, you are storing twice each Company object, you might want to store a reference to the Company in the Table or simply use an OrderedTable if you need to keep the order of the inserted keys.

How can I set transparent walls in a given view with Revit API?

I try to set transparent walls, using pyRevit. I do the following:
categories = List[ElementId]()
wallCatId = ElementId(BuiltInCategory.OST_Walls)
categories.Add(wallCatId)
ogs = OverrideGraphicSettings()
ogs.SetSurfaceTransparency(70)
t = Transaction(doc, "New parameter filter")
t.Start()
filter = ParameterFilterElement.Create(doc, "Walls filter", categories)
t.Commit()
all_views = FilteredElementCollector(doc).OfClass(View).ToElements()
for i in all_views:
if (i.ViewType == ViewType.ThreeD) or (i.ViewType == ViewType.FloorPlan):
views_to_treat.append(i)
t = Transaction(doc, "New visibility filter")
t.Start()
for i in views_to_treat:
i.AddFilter(filter.Id)
i.SetFilterOverrides(filter.Id, ogs)
t.Commit()
Nothing happens, I don't know why. Is it my "categories" that is wrongly defined (how can I know what kind of ElementId it expects? Is it the Id of the Wall Category? In that case, it should be ok here)? Or is it when applying the filter override to the view?
Any help would be greatly appreciated!
Arnaud.
I can see that you are applying the transparency filter to Walls. I am not 100% sure that this is the most efficient way to achieve this since we can override transparency via Category override. Please keep in mind that Filters are limited as we can apply only a handful of them to the view. There is a max number. I don't remember from top of my head, but there is. Also, order of filters matters, as they can potentially override each other's rules based on order. Either way overriding transparency can be achieved by changing it on a Category like so:
catId = ElementId(BuiltInCategory.OST_Walls)
all_views = FilteredElementCollector(doc).OfClass(View).ToElements()
overrides = OverrideGraphicSettings()
overrides.SetSurfaceTransparency(70)
t = Transaction(doc, "Override Categories")
t.Start()
for i in all_views:
if ((i.ViewType == ViewType.ThreeD) or (i.ViewType == ViewType.FloorPlan)) and (i.IsCategoryOverridable(catId)):
try:
i.SetCategoryOverrides(catId, overrides)
except:
# print out error?
pass
t.Commit()
Also, just a few generic comments. Try to minimize the amount of times you iterate over lists, especially if they are the same items. If you can do what you need to do in the first loop, then that's the best. The above can be simplified even more with list comprehension but I wanted to keep it "obvious" for educational purposes.
I am also checking if Category is overridable before attempting to do so. Why? Because if view category overrides are controlled by a view template, it will not allow us to set the overrides. Also some categories don't have a surface transparency override ex. lines if I remember correctly.
Finally I like to put it all in a try/except statement so that i can catch any issues in my loop and still continue with other items. if I don't do that, and one view fails, we would have failed the whole operation.
This is what the result should be:

What is the Python equivalent of a jagged array?

After years of using Excel and learning VBA, I am now trying to learn Python. Here's the scenario:
I asked 7 summer camp counselors which activities they would like to be in charge of. Each student had a random number of responses, and there is no upper limit on the number of activities chosen. However, each activity is unique, and once "claimed" by a student it cannot claimed by any other counselor. The results were:
Adam: archery, canoeing
Bob: frisbee, golf, painting, trampoline
Carol: tennis, dance, skating
Denise: cycling
Eddie: horseback, fencing, soccer
Fiona: painting
George: basketball, football
I'm most familiar with VB (I am an old guy) and in the past I would have stored the above info in a jagged array. But since I'm new to Python, I am confused as to how to do this. I think a list of lists would work for me and here is my code. Let's say I have a list of counselors, and separate lists for each counselors' activities. How do I merge them or put them in one data structure? What am I doing wrong below? Thank you.
counselors = []
counselors = ['Adam','Bob','Carol','Denise','Eddie','Fiona','George']
#create a list of Carol's activities
activities = []
activities = ['tennis','dance','skating']
counselors[2].append[(activities)]
A jagged array in Python is pretty much a list of lists as you mentioned.
I would use a dictionary to store the counselors activity information, where the key is the name of the counselor, and the value is the list of activities the counselor will be in charge of e.g.
counselors_activities = {"Adam": ["archery", "canoeing"],
"Bob": ["frisbee", "golf", "painting", "trampoline"],
"Carol": ["tennis", "dance", "skating"],
"Denise": ["cycling"],
"Eddie": ["horseback", "fencing", "soccer"],
"Fiona": ["painting"],
"George": ["basketball", "football"]}
And access each counselor in the dictionary as such:
counselors_activites["Adam"] # when printed will display the result => ['archery', 'canoeing']
In regards to the question, I would store the list of activities available in a list, and anytime an activity is chosen, remove it from the list and add it to the counselor in the dictionary as such:
list_of_available_activities.remove("archery")
counselors_activities["Adam"].append("archery")
And if a counselor no longer was in charge of the activity, remove it from them and add it back to the list of available activities.
Update: I have provided a more fully fledged solution below based on your requirements from your comments.
Text file, activites.txt:
Adam: archery, canoeing
Bob: frisbee, golf, painting, trampoline
Carol: tennis, dance, skating
Denise: cycling
Eddie: horseback, fencing, soccer
Fiona: painting
George: basketball, football
Code:
#Set of activities available for counselors to choose from
set_of_activities = {"archery",
"canoeing",
"frisbee",
"golf",
"painting",
"trampoline",
"tennis",
"dance",
"skating",
"cycling",
"horseback",
"fencing",
"soccer",
"painting",
"basketball",
"football"}
with open('activities.txt', 'r') as f:
for line in f:
# Iterate over the file and pull out the counselor's names
# and insert their activities into a list
counselor_and_activities = line.split(':')
counselor = counselor_and_activities[0]
activities = counselor_and_activities[1].strip().split(', ')
# Iterate over the list of activities chosen by the counselor and
# see if that activity is free to choose from and if the activity
# is free to choose, remove it from the set of available activities
# and if it is not free remove it from the counselor's activity list
for activity in activities:
if activity in set_of_activities:
set_of_activities.remove(activity)
else:
activities.remove(activity)
# Insert the counselor and their chosen activities into the dictionary
counselors_activities[counselor] = activities
# print(counselors_activities)
I have made one assumption with this new example, which is that you will already have a set of activities that can be chosen from already available:
I made the text file the same format of the counselors and their activities listed in the question, but the logic can be applied to other methods of storage.
As a side note and a correction from my second example previously, I have used a set to represent the list of activities instead of a list in this example. This set will only be used to verify that no counselor will be in charge of an activity that has already been assigned to someone else; i.e., removing an activity from the set will be faster than removing an activity from the list in worst case.
The counselors can be inserted into the dictionary from the notepad file without having to insert them into a list.
When the dictionary is printed it will yield the result:
{"Adam": ["archery", "canoeing"],
"Bob": ["frisbee", "golf", "painting", "trampoline"],
"Carol": ["tennis", "dance", "skating"],
"Denise": ["cycling"],
"Eddie": ["horseback", "fencing", "soccer"],
"Fiona": [], # Empty activity list as the painting activity was already chosen by Bob
"George": ["basketball", "football"]}

python3 wx.TreeCtrl - how to iterate through several levels

I have a treectrl structure which is populated from an external search of an open data set hosted by our municipal government. The data pertains to business licenses and is requested using Pandas and Sodapy. The tree is populated as follows:
for index, row in results_df.iterrows():
tradename = row['tradename']
address = row['address']
licTypes = row['licencetypes']
comm = row['comdistnm']
jobSts = row['jobstatusdesc']
jobCrt = row['jobcreated']
lng = row['longitude']
lng = str(lng)
lat = row['latitude']
lat = str(lat)
# Populate Tree Controls with DataFrame values
trdName = self.thrTree.AppendItem(root, tradename)
self.thrTree.AppendItem(trdName, address)
self.thrTree.AppendItem(trdName, licTypes)
self.thrTree.AppendItem(trdName, comm)
self.thrTree.AppendItem(trdName, jobSts)
self.thrTree.AppendItem(trdName, jobCrt)
self.thrTree.AppendItem(trdName, lng)
self.thrTree.AppendItem(trdName, lat)
This will result in a final structure of root, then node 1 with business name, and when expanded, contains all the information listed above, so I'm assuming root level, then child node 1, then child.child of node 1? Not even sure how the second second indented nodes are called. (I've heard the term leaf for the third level used before) But I digress; what I am interested in is grabbing the Latitude and Longitude of where the business is located, then allowing the user to map the location if they choose. I bind a wx.EVT_TREE_ITEM_ACTIVATED so that when the user double clicks on a business name to get the details, I want to grab the items displayed. This is how I am currently trying to iterate through the child nodes.
item = self.thrTree.GetSelection()
while self.thrTree.GetItemParent(item):
piece = self.thrTree.GetItemText(item)
tmpHldr.insert(0, piece)
item = self.thrTree.GetItemParent(item)
Looking at item, it appears to be collecting all the business names under root, and ignoring the third level items of interest.
What do I need to do to go deeper within the tree to grab the details under the business clicked on, and not just the list of business names under the root item, which is called 'Search Results'?
Thanks!
#YYC_Code,
Did you look here?
This has GetFirstChild()/GetNextChild() pair functions that you can use to iterate. It also has ItemHasChildren() function which you can use to verify if the item has any children and use the pair mentioned above if it does.
EDIT:
[quote]
For this enumeration function you must pass in a ‘cookie’ parameter which is opaque for the application but is necessary for the library to make these functions reentrant (i.e. allow more than one enumeration on one and the same object simultaneously). The cookie passed to GetFirstChild and GetNextChild should be the same variable.
[/quote]
You need to make sure that the cookie parameter is the same during the iteration.
You should also do this:
[quote]
Returns an invalid tree item (i.e. wx.TreeItemId.IsOk returns False) if there are no further children.
[/quote]

Web2Py list:reference table, Load and set data

Maybe I'm missing something absurd, I'm not seeing, but this is my first app to study web2py.
I am unable to enter the data in Table Movies, which has fields related to other tables.
The list is loaded, but it is not registered in Movies registration.
Under the codes and the results.
db.py
Movie = db.define_table('movies',
Field('title','string', label = 'Title'),
Field('date_release','integer', label = 'Date Release'),
Field('duraction','integer', label = 'Duraction'),
Field('category','string','list:reference categories', label = 'Category'),
Field('actor','list:reference actors', label = 'Actor'),
Field('director','list:reference directors', label = 'Diretor'),
)
Category = db.define_table('categories',
Field('title','string', label = 'Title'),
)
validators.py
Movie.title.requires = [IS_NOT_EMPTY(), IS_NOT_IN_DB(db, 'movies.title')]
Movie.category.requires = IS_IN_DB(db, 'categories.title')
Movie.director.requires = IS_IN_DB(db, 'directors.name')
Movie.actor.requires = IS_IN_DB(db, 'actors.name')
Movie.duraction.requires = IS_INT_IN_RANGE(0, 1000)
Category.title.requires = IS_NOT_EMPTY()
movie.py
def add():
form = SQLFORM(Movie)
if form.process().accepted:
response.flash = "Successful! New movie added!"
redirect(URL('add'))
elif form.errors:
response.flash = 'Error'
else:
response.flash = 'Form, set data'
return dict(form = form)
List Load another tables - ok:
The items of list not record in DB:
The widgets displayed in the form are based on the IS_IN_DB field validators you have specified, and there are three problems with the way you have coded them.
First, list:reference fields, like standard reference type fields, store the record IDs of the records they reference -- they do not store values of other fields within the referenced records. So, the second argument to the IS_IN_DB validator should always be the ID field (e.g., categories.id).
Second, although the field will store record IDs, you want the form widget to show some other more descriptive representation of each record, so you should specify the "label" argument of the IS_IN_DB validator (e.g., label='%(title)s').
Third, list:reference fields allow for multiple selections, so you must set the "multiple" argument of the IS_IN_DB validator to True. This will result in a multi-select widget in the form.
So, the resulting validator should look like this:
Movie.category.requires = IS_IN_DB(db, 'categories.id', label='%(title)s', multiple=True)
The above will allow multiple db.categories IDs to be selected, though the form widget will display category titles rather than the actual IDs.
Now, all of the above can be made much easier if you instead define the referenced tables before the db.movies table and specify a format argument for each table:
Category = db.define_table('categories',
Field('title','string', label = 'Title'),
format='%(title)s')
Movie = db.define_table('movies',
...,
Field('category', 'list:reference categories', label = 'Category'),
...)
With the above code, there is no need to explicitly specify the IS_IN_DB validator at all, as the db.movies.category field will automatically get a default validator exactly like the one specified above (the format attribute of the db.categories table is used as the label argument).
You might want to read the documentation on list:reference fields and the IS_IN_DB validator.
As an aside, you might consider specifying your field validators within the table definitions (via the requires argument to Field()), as this is more concise, keeps all schema-related details in one place, and eliminates the need to read and execute an additional model file on every request.

Resources